3. Use HistoryPanel to operate on and analyze historical data
After completing the first two chapters, “Getting Started” and “Obtaining and Managing Financial Data,” we can already:
Configure the data source locally and download the required historical data;
Use
get_history_data()orget_history_panel()to obtain basic market data such as prices and trading volume.
Building on this, this chapter will use a complete small example to demonstrate how to use HistoryPanel:
Unified management of historical data for multiple instruments and multiple indicators;
Calculate simple factors such as returns and volatility;
Overlay K-line technical indicators;
Identify some candlestick patterns;
Use
whereto generate a bool mask with the same shape as the data, in preparation for subsequent research APIs that takemask=;Use column attributes (e.g.,
hp.close), comparison operators (returning anumpyboolean array that can be chained withwhere), andhp.loc(filters only along the time axis, equivalent tohp[:, :, key]).
For a more complete API description, see the HistoryPanel API Reference (including the dedicated sections on “column attribute access, comparisons, and
loc” and onwhere).
3.1. 1. 准备数据:获取一个 HistoryPanel
Suppose we have already downloaded the past year of daily data for the CSI 300 Index (000300.SH). We can directly obtain a HistoryPanel via qt.get_kline() or qt.get_history_data():
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)
At this point:
hp.shapeis a(L, R, C)tuple, corresponding to number of stocks × time length × number of data types;hp.sharesis usually['000300.SH'];By default,
hp.htypesincludes['open', 'high', 'low', 'close', 'vol'], etc.
3.2. 2. 计算收益率与波动率因子
HistoryPanel has built-in interfaces for calculating returns and volatility for price series:
# 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())
A typical usage is:
Treat
retorvolas a factor value matrix (time × stocks);It can be fed directly into the backtesting engine, or used for stock selection, scoring, etc.
3.3. 3. 叠加 K 线技术指标
When HistoryPanel contains OHLC data, you can access a set of commonly used technical indicators via hp.kline:
# 基于 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())
Here:
hp.kline.sma(...)will append a column tohtypes, such assma_20;hp.kline.macd(...)will append three columns:macd_xxx,macd_signal_xxx, andmacd_hist_xxx;Finally, use
to_share_frame(share)to obtain a standard 2DDataFrame, which can be directly analyzed jointly with other data sources.
3.1 inplace: expand columns in place (reduce copying)
By default, kline.* returns a new HistoryPanel (without mutating the original object). When you want to append multiple columns continuously on the same panel and reduce intermediate objects, you can use inplace=True:
# 在原面板上原地追加两列,并返回原面板(支持链式)
hp2 = hp.kline.sma(window=20, inplace=True).kline.macd(inplace=True)
assert hp2 is hp
print('原地扩列后的 htypes:', hp.htypes)
Tip: When a new column name conflicts with an existing column name,
kline.*will raise an EnglishValueErrorto avoid silent overwriting.
3.2 First entry point: research_preset (one-line column chaining + direct plotting)
If you want to “get a decent-looking chart as soon as you have the data”, it’s recommended to use research_preset directly to generate the official preset set of commonly used columns (the preset only does explicit appends and does not hide computations inside plot()):
# 生成 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)
When the input panel is missing the input columns required by the preset, an English ValueError will be raised; the message includes the list of missing columns and suggestions on how to fill them in.
3.4. 4. 使用 assign 一次派生多列
In practical research, you often need to derive multiple new factors from existing columns, such as spreads, ratios, scaled normalized values, and so on. HistoryPanel.assign() provides a DSL for deriving multiple columns in one go:
# 假设 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:])
The keyword argument names of
assignare the new column names (htype); the values can be callables that take the currentHistoryPanel, or arrays/scalars that can be broadcast to(M,L);Within the same call, later columns can depend on columns that were just added earlier, for example:
hp3 = hp.assign(
a=lambda p: p['close'].values[:, :, 0] + 1.0,
b=lambda p: p['a'].values[:, :, 0] * 2.0,
)
By default, inplace=False, which returns a new panel without modifying the original object; when you want to expand columns in place on the original panel, you can use:
hp.assign(inplace=True, factor=lambda p: p['close'].values[:, :, 0] / 100.0)
Tip: Like
hp['col'] = ...,assignuses “overwrite” semantics for existing column names; callingassignon an empty panel will raise an EnglishValueError.
3.5. 5. 横截面 rank 与 zscore(cs/ts)
In multi-asset research, two common types of standardization needs are:
Cross-sectional: fix a trading day and standardize or rank factor values across all stocks;
Time-series (rolling): fix a stock and use a rolling window to standardize its own historical series.
HistoryPanel.rank() and HistoryPanel.zscore() provide the most basic transformations of these two types (transformations only, no regression inference):
# 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'
Note:
zscore(method='cs')andzscore(method='ts')have completely different semantics. Be sure to explicitly specifymethod(and providewindowin thetscase) to avoid mistaking “cross-sectional standardization” for “time-series standardization”.
5.5 Alignment and resampling (align_to / resample): avoid silent row misalignment
So when you have two HistoryPanels and want to do element-wise operations (such as hp_factor / hp_price or subtracting two panels), you need to pay special attention:
HistoryPanelwill not automatically align two panels inside operators;If
shares/hdatesare inconsistent, doing NumPy operations directly on.valuescan easily produce results that “seem to compute, but are actually misaligned by rows”.
Therefore, it’s recommended to follow a fixed workflow: align first, then compute.
# 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)
=When you need to convert daily data into lower-frequency data such as weekly/monthly, you can use resample() and explicitly specify the aggregation rule for each column (to avoid unclear semantics):
# 典型 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], '...')
Note:
resample()requiresaggto cover allhtypes. For custom factor columns that are not OHLCV, you need to decide the aggregation method yourself (e.g.,'last'/'mean') and include it inagg.
5.6 Use an (M, L) mask to highlight plots (event day / signal day)
In factor research, you will often get a 2D boolean matrix mask_ml, indicating “on which dates each stock meets the condition” (e.g., event dates, signal dates, tradable universe).
HistoryPanel.plot() with highlight={'condition': mask_ml} supports mapping it directly onto the chart to highlight points:
If
mask_ml.shape == (M_plot, L), whereM_plotis the number of stocks currently selected byplot(shares=...), it corresponds in the plotting share order;If
mask_ml.shape == (M_all, L), whereM_all == len(hp.shares), then extract the current plotting subset by share name (to avoid silent row misalignment);When
layout='overlay', highlights are shown as primary-only by default (consistent with existing Plotly highlight semantics).
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 (background: not implemented in this section)
In empirical econometrics, you may see Newey–West (HAC) robust standard errors and Fama–MacBeth two-step regressions. They are mainly used for regression inference (standard error correction, significance tests for risk premia), and are not “basic transformations” like HistoryPanel.zscore(). If you need publication-grade empirical work, it’s recommended to export HistoryPanel to a DataFrame and then use tools such as statsmodels to complete the inference workflow.
3.6. 6. 使用 apply_ta 统一调用技术指标
If you are already familiar with qteasy.tafuncs or ta-lib, you can use HistoryPanel.apply_ta() for a unified wrapper:
# 在所有股票上应用 '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)
This avoids manually calling tafuncs.sma in a loop; HistoryPanel will automatically broadcast the computation across all selected stocks.
3.7. 7. 识别蜡烛形态
For OHLC data, you can also wrap ta-lib’s candlestick pattern recognition functions via HistoryPanel.candle_pattern(), for example:
# 识别锤头线形态(CDLHAMMER),返回时间 × 股票的整数信号矩阵
hammer = hp.candle_pattern(
name='cdlhammer',
as_panel=False, # 返回 DataFrame
)
print('锤头线信号(非 0 代表出现形态):\n', hammer[hammer != 0].dropna(how='all'))
The signal meaning is usually:
0: the pattern is not detected;Positive / negative values: pattern signals in different directions or with different strengths (depending on the specific ta-lib function’s definition).
3.8. 8. 生成研究用 bool 掩码(where)
The axis order of panel values is (number of assets M, time length L, number of indicator columns N), consistent with hp.shape. hp.where(condition) returns a same-shape numpy.ndarray, dtype=bool, and does not modify hp. cum_return, normalize, and portfolio already support mask=hp.where(...) (or a bool array with the same broadcasting rules as where). For research-oriented semantics and edge cases (path breaks, invalid base dates, portfolio–benchmark relationships, etc.), refer to the API docstring.
Compare against the full table of values (equivalent to hp.values > threshold):
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()))
Label along the time dimension (e.g., if the index of a certain column is an “event day”, replicate it across all htypes; the following is illustrative—make sure cond_ml has shape (M, L)):
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()
Build conditions based on the close-price column (the callable receives the current HistoryPanel):
# 假设存在 'close' 列;多列面板可用 p.values[:, :, idx] 或子面板
mask_close = hp.where(lambda p: p['close'].values[:, :, 0] > 0)
Composite conditions and missing values:
mask_band = hp.where(lambda p: (p.values > -1e9) & (p.values < 1e9))
mask_nan = hp.where(np.isnan(hp.values)) # 标记 NaN 所在格,供排除缺失
Note: A 2D condition with shape exactly (M, L) means “for each (asset, date), all indicator columns share the same boolean value”. The library will first expand it to (M, L, 1) and then broadcast to (M, L, N). A 1D (M,) or (M, 1) means it varies only by asset; it will be expanded to (M, 1, 1) and then broadcast. For more complete parameter and exception notes, see the where section in HistoryPanel API Reference.
8.1 Cumulative return and normalization (cum_return / normalize)
Returns a new HistoryPanel (does not modify the original panel). By default, it is computed on the parsed close column; output column names are cumret_<column_name> and norm_<column_name> (when coexisting with the adjusted-price column close|b, you can still pass htypes=None, and the column name is still cumret_close). Can be used together with a mask:
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 aggregation (portfolio)
Research-oriented: take an equal-weighted or weighted average of specified columns along the asset dimension, and return a new HistoryPanel (hdates unchanged). mask= follows the same broadcasting rules as where. When there are no groups and a benchmark is specified, the benchmark asset does not participate in the combination calculation; it is only used for benchmark_output='tag_along' (one extra row for the benchmark curve) or 'excess_only' (output excess_<column name>). Not an account backtest; no transaction costs. See the API docstring for details.
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 Column attributes, comparisons, and loc (a brief note on differences from pandas)
Column attributes (read-only): when a column name is a valid Python identifier, you can use
hp.close, equivalent tohp['close']; adjusted-price column names containing|and the like must use brackets. For assignment, consistently usehp['col'] = ....Comparison: expressions like
hp.close > hp.openandhp > 0yield anumpy.ndarray(bool), not a sub-HistoryPanel; a full-shape mask can be written ashp.where(hp.close > 15). When both sides are panels,sharesandhdatesmust match; when comparing two columns, it is usually two single-column sub-panels.loc:hp.loc[s]is equivalent tohp[:, :, s]; it filters only along the time (hdates) axis. Do not pass the(M, L, N)mask produced bywhereintoloc; grid-point conditions should still usewhere+ a subsequentmask=.
For details and edge cases, refer to the HistoryPanel API Reference (including where, cum_return, normalize, portfolio).
# 示意(在已含 open/close 列的 hp 上)
# m = hp.where(hp.close > hp.open) # 比较 → bool ndarray → where 广播到 (M,L,N)
# tail = hp.loc[-5:] # 仅时间轴,等价 hp[:, :, -5:]
3.9. 9. 从 HistoryPanel 研究到 Strategy / Operator:迁移路径
cum_return, portfolio, plot, etc. on HistoryPanel are research-oriented tools: convenient for eyeballing factors and viewing rough portfolio curves, and do not include account semantics consistent with formal backtests, such as settlement cycles, fees, slippage, and parsing of signal types (PT/PS/VS). If the conclusions are to be used for live trading or serious evaluation, you should write rules with the same economic meaning into the strategy’s realize(), and hand them to Operator + Backtester.
Below, we use the same idea—20-period simple momentum to connect the two stages (requires that a data source has been configured locally and the corresponding fields have been downloaded; stock codes are for illustration only).
9.1 Research stage: compute factors on a multi-asset HistoryPanel and take a quick look at cross-sections
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 高亮更佳
Here you have completed factor-column generation, cross-sectional transforms, and rough aggregation on a 3D panel; if the results look good, proceed to 9.2 and move the factor definition into the strategy.
9.2 Formal backtesting stage: FactorSorter + Operator
Stock-selection strategies commonly use FactorSorter: realize() returns one scalar factor per instrument (length = number of stocks), and the framework then sorts and assigns weights based on max_sel_count, sort_ascending, etc. The momentum below has the same economic meaning as returns(..., periods=20) in 9.1, but the data comes from injection via the strategy window (aligned with backtest steps and constrained by use_latest_data_cycle, etc.).
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、起止日期与回测配置
Note:
By default,
Operatorallows using PT-class target-position signals within groups; howFactorSorteris paired with PT should follow the strategy documentation in your project.Ranks/z-scores computed in HP using the full sample may differ slightly numerically from factors in backtests where “each step only sees the historical window”; this is expected. You should re-check using
get_datainside the strategy to guard against look-ahead bias.
For the complete runnable script, see examples/historypanel_research_to_strategy.py in the project root directory.
3.10. 10. 多源数据拼成 HistoryPanel(价格 + 基本面示例)
HistoryPanel can hold any htypes columns; a common approach for multiple tables / multiple DataTypes is: (1) fetch multiple columns in one get_history_data call; (2) or fetch two panels separately and then align_to.
10.1 Fetch multiple columns in one go (frequency and asset type must be consistent)
If you already have daily close and pb (price-to-book) fields locally, you can directly:
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')
If a column is missing or a table has not been downloaded, get_history_data will raise an error; please first complete the table data via the data source tutorial.
10.2 Align two panels first, then derive (different sources / different pulls)
align_to requires that the htypes names and order match exactly on both sides. If you only fetched the close and pb columns separately, you need to first expand them into a homogeneous set of columns (use NaN as placeholders on the missing side), then after alignment use assign to write the valid values back into the same set of column names:
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],
)
The same applies to more fields (industry, market cap, etc.); if you can fetch them in one call like get_history_data(htypes='close, pb', ...), prefer §10.1 to avoid one manual homogenization step. The core principle is still align first, then compute, to avoid NumPy silently misaligning rows.
For a runnable synthetic-data demo, see examples/historypanel_multisource_research.py in the project root directory.
3.11. 11. 导出到 pandas / statsmodels(宽表、长表与截面回归)
HistoryPanel does not have built-in inference workflows such as Newey–West or Fama–MacBeth; when you need publication-grade regressions, you should export to a 2D table and then use libraries such as statsmodels.
11.1 Split by asset into multiple DataFrames (wide table)
by_share = hp.to_df_dict(by='share')
df_000001 = by_share['000001.SZ'] # index 为时间,columns 为 htypes
11.2 Manually stacking a growth table (for easy daily cross-sectional grouping)
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 Single-day cross-sectional OLS example (requires statsmodels installed)
# 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())
The dependent variables above are for demonstration only; in practice you should align factors with forward returns and handle suspensions, survivorship bias, etc.—all of which should be done at the econometrics layer, not as HistoryPanel’s core responsibility.
For a synthetic-data example, see examples/historypanel_statsmodels_export.py in the project root directory.
3.12. 12. 小结与下一步
Through the examples in this chapter, you can see:
HistoryPanelprovides a unified 3D historical-data container, suitable for joint research across multiple instruments / multiple indicators;On top of it, you can directly call statistical methods (
describe/mean/std/min/max), rolling windows (rolling), returns/volatility (returns/volatility), cross-sectional ranking and standardization (rank/zscore), cumulative returns and normalization (cum_return/normalize), portfolio aggregation (portfolio), candlestick (K-line) indicators (kline.*), technical indicator bridging (apply_ta), candlestick pattern recognition (candle_pattern), research masks (where), as well as column attributes / comparisons /loc, etc.;At any step, you can easily switch back to a
DataFrameto work together with pandas / sklearn / statsmodels, etc.;For formal backtesting, use
Strategy.realize+Operator; for the boundary and migration path between research-oriented HP and the backtest engine, see §9.
Recommended next steps:
In a Notebook, build a
HistoryPanelbased on your own stock universe, try stacking multiple factors (moving averages, volatility, valuation, etc.), and plot charts aligned with prices;Based on reading the HistoryPanel API reference, design a workflow for your research topic: “data → factors (HP) → rule solidification (Strategy) → backtest (Operator)”.
If you are considering whether to add a separate “factor evaluation” API wrapper, you can read the design note HistoryPanel and the optional FactorResearch layer.