3. Utilice HistoryPanel para operar y analizar datos históricos

Después de completar los dos primeros capítulos, «Cómo comenzar» y «Obtener y administrar datos financieros», ya podemos:

  • Configure la fuente de datos localmente y descargue los datos históricos requeridos;

  • Utilice get_history_data() o get_history_panel() para obtener datos básicos del mercado, como precios y volumen de operaciones.

Sobre la base de esto, este capítulo utilizará un pequeño ejemplo completo para demostrar cómo usar HistoryPanel:

  • Gestión unificada de datos históricos para múltiples instrumentos y múltiples indicadores;

  • Calcule factores simples como rendimientos y volatilidad;

  • Superposición de indicadores técnicos de línea K;

  • Identificar algunos patrones de velas;

  • Utilice where to generate a bool mask with the same shape as the data, in preparation for subsequent research APIs that take mask=;

  • Utilice atributos de columna (por ejemplo, hp.close), comparison operators (returning a numpy boolean array that can be chained with where), and hp.loc (filters only along the time axis, equivalent to hp[:, :, key]).

Para obtener una descripción más completa de la API, consulte la HistoryPanel Referencia de API (incluidas las secciones dedicadas a “acceso a atributos de columna, comparaciones y loc” and on where).


3.1. 1. 准备数据:获取一个 HistoryPanel

Supongamos que ya hemos descargado los datos diarios del año pasado para el índice CSI 300 (000300.SH). Podemos obtener directamente un HistoryPanel mediante qt.get_kline() o 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)

En este punto:

  • hp.shape es una tupla (L, R, C), correspondiente a número de existencias × duración × número de tipos de datos;

  • hp.shares suele ser ['000300.SH'];

  • De forma predeterminada, hp.htypes incluye ['open', 'high', 'low', 'close', 'vol'], etc.


3.2. 2. 计算收益率与波动率因子

HistoryPanel tiene interfaces integradas para calcular los rendimientos y la volatilidad de las series de precios:

# 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())

Un uso típico es:

  • Trate ret o vol como una matriz de valor de factor (tiempo × existencias);

  • Puede introducirse directamente en el motor de backtesting o utilizarse para selección de valores, puntuación, etc.


3.3. 3. 叠加 K 线技术指标

Cuando HistoryPanel contiene datos OHLC, puede acceder a un conjunto de indicadores técnicos de uso común a través de 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())

Aquí:

  • hp.kline.sma(...) agregará una columna a htypes, como sma_20;

  • hp.kline.macd(...) agregará tres columnas: macd_xxx, macd_signal_xxx y macd_hist_xxx;

  • Finalmente, use to_share_frame(share) para obtener un DataFrame 2D estándar, que se puede analizar directamente junto con otras fuentes de datos.

3.1 in situ: expandir columnas en su lugar (reducir la copia)

De forma predeterminada, kline.* devuelve un nuevo HistoryPanel (sin mutar el objeto original). Cuando desee agregar varias columnas continuamente en el mismo panel y reducir objetos intermedios, puede usar inplace=True:

# 在原面板上原地追加两列,并返回原面板(支持链式)
hp2 = hp.kline.sma(window=20, inplace=True).kline.macd(inplace=True)
assert hp2 is hp
print('原地扩列后的 htypes:', hp.htypes)

Consejo: Cuando un nombre de columna nuevo entra en conflicto con un nombre de columna existente, kline.* generará un ValueError en inglés para evitar una sobrescritura silenciosa.

3.2 Primer punto de entrada: research_preset (encadenamiento de columnas de una línea + trazado directo)

Si desea «obtener un gráfico de aspecto decente tan pronto como tenga los datos», se recomienda usar research_preset directamente para generar el conjunto preestablecido oficial de columnas de uso común (el valor preestablecido solo agrega anexos explícitos y no oculta cálculos dentro de 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)

Cuando al panel de entrada le faltan las columnas de entrada requeridas por el ajuste preestablecido, se generará un ValueError en inglés; el mensaje incluye la lista de columnas que faltan y sugerencias sobre cómo completarlas.


3.4. 4. 使用 assign 一次派生多列

En la investigación práctica, a menudo es necesario derivar múltiples factores nuevos de columnas existentes, como diferenciales, proporciones, valores normalizados escalados, etc. HistoryPanel.assign() proporciona un DSL para derivar múltiples columnas de una sola vez:

# 假设 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:])
  • Los nombres de los argumentos de las palabras clave de assign son los nuevos nombres de las columnas (htype); los valores pueden ser invocables que toman el HistoryPanel actual, o matrices/escalares que se pueden transmitir al (M,L);

  • Dentro de la misma llamada, las columnas posteriores pueden depender de las columnas que se acaban de agregar anteriormente, por ejemplo:

hp3 = hp.assign(
    a=lambda p: p['close'].values[:, :, 0] + 1.0,
    b=lambda p: p['a'].values[:, :, 0] * 2.0,
)

Por defecto, inplace=False, que devuelve un nuevo panel sin modificar el objeto original; cuando desee expandir las columnas en su lugar en el panel original, puede usar:

hp.assign(inplace=True, factor=lambda p: p['close'].values[:, :, 0] / 100.0)

Consejo: Al igual que hp['col'] = ..., assign utiliza semántica de «sobrescritura» para los nombres de columnas existentes; llamar a assign en un panel vacío generará un ValueError en inglés.


3.5. 5. 横截面 rank 与 zscore(cs/ts)

En la investigación de activos múltiples, dos tipos comunes de necesidades de estandarización son:

  • Transversal: fije un día de negociación y estandarice o clasifique los valores de los factores en todas las acciones;

  • Series temporales (móviles): corrige una acción y utiliza una ventana móvil para estandarizar su propia serie histórica.

HistoryPanel.rank() y HistoryPanel.zscore() proporcionan las transformaciones más básicas de estos dos tipos (solo transformaciones, sin inferencia de regresión):

# 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'

Nota: zscore(method='cs') y zscore(method='ts') tienen semánticas completamente diferentes. Asegúrese de especificar explícitamente method (y proporcionar window en el caso ts) para evitar confundir «estandarización transversal» con «estandarización de series temporales».

5.5 Alineación y remuestreo (align_to / resample): evite la desalineación silenciosa de las filas

Entonces, cuando tiene dos HistoryPanel y desea realizar operaciones de elementos (como hp_factor / hp_price o restar dos paneles), debe prestar especial atención:

  • HistoryPanel no alineará automáticamente dos paneles dentro de los operadores;

  • Si shares / hdates son inconsistentes, realizar operaciones NumPy directamente en .values puede producir fácilmente resultados que «parecen calcularse, pero en realidad están desalineados por filas».

Por lo tanto, se recomienda seguir un flujo de trabajo fijo: alinear primero, luego calcular.

# 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)

=Cuando necesite convertir datos diarios en datos de menor frecuencia, como semanales/mensuales, puede usar resample() y explícitamente especificar la regla de agregación para cada columna (para evitar una semántica poco clara):

# 典型 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], '...')

Nota: resample() requiere agg para cubrir todo el htypes. Para las columnas de factores personalizados que no son OHLCV, debe decidir usted mismo el método de agregación (por ejemplo, 'last' / 'mean') e incluirlo en agg.

5.6 Utilice una máscara (M, L) para resaltar gráficos (día del evento/día de la señal)

En la investigación de factores, a menudo obtendrá una matriz booleana 2D mask_ml, que indica «en qué fechas cada acción cumple la condición» (por ejemplo, fechas de eventos, fechas de señales, universo negociable).

HistoryPanel.plot() con highlight={'condition': mask_ml} admite mapearlo directamente en el gráfico para resaltar puntos:

  • Si mask_ml.shape == (M_plot, L), donde M_plot es el número de acciones actualmente seleccionadas por plot(shares=...), corresponde en el orden de acciones trazado;

  • Si es mask_ml.shape == (M_all, L), donde M_all == len(hp.shares), extraiga el subconjunto de trazado actual por nombre compartido (para evitar una desalineación silenciosa de filas);

  • Cuando layout='overlay', los resaltados se muestran como solo primario de forma predeterminada (consistente con la semántica de resaltado existente de Plotly).

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 (antecedentes: no implementado en esta sección)

En econometría empírica, es posible que vea errores estándar robustos de Newey-West (HAC) y regresiones de dos pasos de Fama-MacBeth. Se utilizan principalmente para inferencia de regresión (corrección de error estándar, pruebas de significancia para primas de riesgo) y no son “transformaciones básicas” como HistoryPanel.zscore(). Si necesita trabajo empírico de nivel de publicación, se recomienda exportar HistoryPanel a DataFrame y luego usar herramientas como statsmodels para completar el flujo de trabajo de inferencia.


3.6. 6. 使用 apply_ta 统一调用技术指标

Si ya está familiarizado con qteasy.tafuncs o ta-lib, puede usar HistoryPanel.apply_ta() para un contenedor unificado:

# 在所有股票上应用 '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)

Esto evita llamar manualmente a tafuncs.sma en un bucle; HistoryPanel transmitirá automáticamente el cálculo a todas las acciones seleccionadas.


3.7. 7. 识别蜡烛形态

Para datos OHLC, también puede ajustar las funciones de reconocimiento de patrones de velas de ta-lib a través de HistoryPanel.candle_pattern(), por ejemplo:

# 识别锤头线形态(CDLHAMMER),返回时间 × 股票的整数信号矩阵
hammer = hp.candle_pattern(
    name='cdlhammer',
    as_panel=False,          # 返回 DataFrame
)
print('锤头线信号(非 0 代表出现形态):\n', hammer[hammer != 0].dropna(how='all'))

El significado de la señal suele ser:

  • 0: no se detecta el patrón;

  • Valores positivos/negativos: señales de patrón en diferentes direcciones o con diferentes intensidades (dependiendo de la definición de la función ta-lib específica).


3.8. 8. 生成研究用 bool 掩码(where

El orden de los ejes del panel. ⟦CÓDIGO0⟧⟦CÓDIGO1⟧⟦CÓDIGO2⟧⟦CÓDIGO3⟧⟦CÓDIGO4⟧⟦CÓDIGO5⟧⟦CÓDIGO6⟧⟦CÓDIGO7⟧⟦CÓDIGO8⟧⟦CÓDIGO9⟧⟦CÓDIGO10⟧⟦CÓDIGO11⟧⟦C ODE12⟧, normalize, and portfolio already support mask=hp.where(...) (or a bool array with the same broadcasting rules as where). Para conocer la semántica orientada a la investigación y los casos extremos (rupturas de ruta, fechas base no válidas, relaciones entre cartera y puntos de referencia, etc.), consulte la cadena de documentación de API.

Compare con la tabla completa de valores (equivalente a 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()))

Etiquete a lo largo de la dimensión de tiempo (por ejemplo, si el índice de una determinada columna es un «día de evento», repítalo en todos los tipos h; lo siguiente es ilustrativo; asegúrese de 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()

Condiciones de compilación basadas en la columna de precio de cierre (el callable receives the current HistoryPanel):

# 假设存在 'close' 列;多列面板可用 p.values[:, :, idx] 或子面板
mask_close = hp.where(lambda p: p['close'].values[:, :, 0] > 0)

Condiciones compuestas y valores faltantes:

mask_band = hp.where(lambda p: (p.values > -1e9) & (p.values < 1e9))
mask_nan = hp.where(np.isnan(hp.values))   # 标记 NaN 所在格,供排除缺失

Nota: Una condición 2D con forma exacta ⟦CÓDIGO0⟧⟦CÓDIGO1⟧⟦CÓDIGO2⟧⟦CÓDIGO3⟧⟦CÓDIGO4⟧⟦CÓDIGO5⟧⟦CÓDIGO 6⟧⟦CÓDIGO7⟧⟦CÓDIGO8⟧⟦CÓDIGO9⟧⟦CÓDIGO10⟧⟦CÓDIGO11⟧⟦CÓDIGO12⟧ sección en HistoryPanel Referencia de API.

8.1 Retorno acumulado y normalización (cum_return / normalize)

Devuelve un nuevo ⟦CÓDIGO0⟧⟦CÓDIGO1⟧⟦CÓDIGO2⟧⟦CÓDIGO3⟧⟦CÓDIGO4⟧⟦CÓDIGO5⟧⟦CÓDIGO6 ⟧⟦CÓDIGO7⟧⟦CÓDIGO8⟧⟦CÓDIGO9⟧⟦CÓDIGO10⟧⟦CÓDIGO11⟧⟦CÓDIGO12⟧). Se puede utilizar junto con una mascarilla:

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 Agregación de cartera (portfolio)

Orientado a la investigación: tome un promedio ponderado o igual de columnas específicas a lo largo de la dimensión de activo y devuelva un nuevo ⟦CÓDIGO0⟧⟦CÓDIGO1⟧⟦CÓDIGO2⟧⟦CÓDIGO3⟧⟦CÓDIGO4⟧⟦CÓDIGO5⟧⟦CÓDIGO6⟧⟦CÓDIGO7⟧⟦CÓDIGO8⟧⟦ CÓDIGO9⟧⟦CÓDIGO10⟧⟦CÓDIGO11⟧⟦CÓDIGO12⟧⟦CÓDIGO13⟧⟦CÓDIGO14⟧⟦CÓDIGO15⟧⟦CÓDIGO16⟧). No es una prueba retrospectiva de la cuenta; sin costos de transacción. Consulte la cadena de documentación API para obtener más detalles.

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 Atributos de columna, comparaciones y loc (una breve nota sobre las diferencias con los pandas)

  • Atributos de columna (solo lectura): cuando el nombre de una columna es un identificador de Python válido, puede usar hp.close, equivalent to hp['close']; adjusted-price column names containing | and the like must use brackets. For assignment, consistently use hp['col'] = ....

  • Comparación: expresiones como ⟦CÓDIGO0⟧⟦CÓDIGO1⟧⟦CÓDIGO2⟧⟦CÓDIGO3⟧⟦CÓDIGO4⟧⟦CÓDIGO5⟧⟦CÓDIGO 6⟧⟦CÓDIGO7⟧⟦CÓDIGO8⟧⟦CÓDIGO9⟧⟦CÓDIGO10⟧⟦CÓDIGO11⟧⟦CÓDIGO12⟧ debe coincidir; al comparar dos columnas, generalmente se trata de dos subpaneles de columna única.

  • **⟦CÓDIGO0⟧⟦CÓDIGO1⟧⟦CÓDIGO2⟧⟦CÓDIGO3⟧⟦CÓDIGO4⟧⟦CÓDIGO5⟧⟦CÓDIGO6⟧⟦CÓDIGO 7⟧⟦CÓDIGO8⟧⟦CÓDIGO9⟧⟦CÓDIGO10⟧⟦CÓDIGO11⟧⟦CÓDIGO12⟧⟦CÓDIGO13⟧⟦CÓDIGO14⟧.

Para obtener detalles y casos extremos, consulte la HistoryPanel Referencia de API (incluido 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. en HistoryPanel son herramientas orientadas a la investigación: convenientes para observar factores y ver curvas aproximadas de cartera, y no incluyen semántica de cuenta consistente con pruebas retrospectivas formales, como ciclos de liquidación, tarifas, deslizamiento y análisis de tipos de señales. (PT/PS/VS). Si las conclusiones se van a utilizar para operaciones reales o para una evaluación seria, debe escribir reglas con el mismo significado económico en el realize() de la estrategia y entregárselas al Operator + Backtester.

A continuación, utilizamos la misma idea: impulso simple de 20 períodos para conectar las dos etapas (requiere que se haya configurado una fuente de datos localmente y se hayan descargado los campos correspondientes; los códigos de acciones son solo para ilustración).

9.1 Etapa de investigación: calcular factores en un multiactivo HistoryPanel y echar un vistazo rápido a las secciones transversales

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 高亮更佳

Aquí ha completado la generación de columnas de factores, transformaciones de sección transversal y agregación aproximada en un panel 3D; Si los resultados parecen buenos, continúe con 9.2 y mueva la definición de factor a la estrategia.

9.2 Etapa de backtesting formal: FactorSorter + Operator

Las estrategias de selección de acciones comúnmente usan FactorSorter: realize() devuelve un factor escalar por instrumento (longitud = número de acciones), y luego el marco clasifica y asigna ponderaciones según max_sel_count, sort_ascending, etc. El siguiente impulso tiene el mismo significado económico que returns(..., periods=20) en 9.1, pero los datos provienen de la inyección a través de la ventana de estrategia (alineada con los pasos de backtest y restringida por 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、起止日期与回测配置

Nota:

  • De forma predeterminada, Operator permite usar señales de posición objetivo de clase PT dentro de grupos; cómo se combina FactorSorter con PT debe seguir la documentación de la estrategia en su proyecto.

  • Los rangos/puntuaciones z calculados en HP utilizando la muestra completa pueden diferir ligeramente numéricamente de los factores en las pruebas retrospectivas donde “cada paso solo ve la ventana histórica”; esto es lo esperado. Debe volver a verificar usando get_data dentro de la estrategia para protegerse contra el sesgo de anticipación.

Para obtener el script ejecutable completo, consulte examples/historypanel_research_to_strategy.py en el directorio raíz del proyecto.


3.10. 10. 多源数据拼成 HistoryPanel(价格 + 基本面示例)

HistoryPanel puede contener cualquier columna htypes; un enfoque común para múltiples tablas/múltiples tipos de datos es: (1) recuperar múltiples columnas en una llamada get_history_data; (2) o busque dos paneles por separado y luego align_to.

10.1 Obtener varias columnas de una sola vez (la frecuencia y el tipo de activo deben ser coherentes)

Si ya tienes los campos diarios close y pb (precio para reservar) localmente, puedes directamente:

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')

Si falta una columna o no se ha descargado una tabla, get_history_data generará un error; Primero complete los datos de la tabla a través del tutorial de fuente de datos.

10.2 Primero alinee dos paneles y luego derive (fuentes diferentes/tirones diferentes)

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 para escribir los valores válidos nuevamente en el mismo conjunto de nombres de columnas:

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],
)

Lo mismo se aplica a más campos (industria, capitalización de mercado, etc.); si puede recuperarlos en una llamada como get_history_data(htypes='close, pb', ...), prefiera §10.1 para evitar un paso de homogeneización manual. El principio básico sigue siendo alinear primero y luego calcular, para evitar que NumPy desalinee filas silenciosamente.

Para ver una demostración ejecutable de datos sintéticos, consulte examples/historypanel_multisource_research.py en el directorio raíz del proyecto.


3.11. 11. 导出到 pandas / statsmodels(宽表、长表与截面回归)

HistoryPanel no tiene flujos de trabajo de inferencia integrados como Newey–West o Fama–MacBeth; cuando necesite regresiones de grado de publicación, debe exportar a una tabla 2D y luego usar bibliotecas como statsmodels.

11.1 Dividir por activo en múltiples DataFrame (tabla amplia)

by_share = hp.to_df_dict(by='share')
df_000001 = by_share['000001.SZ']   # index 为时间,columns 为 htypes

11.2 Apilar manualmente una tabla de crecimiento (para agrupar fácilmente las secciones transversales diarias)

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 Ejemplo de OLS transversal de un solo día (requiere statsmodels instalados)

# 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())

Las variables dependientes anteriores son sólo para demostración; en la práctica, se deben alinear los factores con los rendimientos futuros y manejar las suspensiones, el sesgo de supervivencia, etc., todo lo cual debe hacerse en el nivel econométrico, no como responsabilidad principal de HistoryPanel.

Para ver un ejemplo de datos sintéticos, consulte examples/historypanel_statsmodels_export.py en el directorio raíz del proyecto.


3.12. 12. 小结与下一步

A través de los ejemplos de este capítulo, puede ver:

  • HistoryPanel proporciona un contenedor unificado de datos históricos en 3D, adecuado para la investigación conjunta entre múltiples instrumentos/múltiples indicadores;

  • Además, puede llamar directamente a métodos estadísticos (describe/mean/std/min/max), ventanas móviles (rolling), rendimientos/volatilidad (returns/volatility), clasificación transversal y estandarización (rank/zscore), rendimiento acumulativo y normalización. (cum_return/normalize), agregación de cartera (portfolio), indicadores de velas (línea K) (kline.*), indicador técnico puente (apply_ta), reconocimiento de patrones de velas (candle_pattern), máscaras de investigación (where), así como columna atributos / comparaciones / loc, etc.;

  • En cualquier paso, puede volver fácilmente a DataFrame para trabajar junto con pandas/sklearn/statsmodels, etc.;

  • Para backtesting formal, utilice Strategy.realize + Operator; Para conocer los límites y la ruta de migración entre HP orientado a la investigación y el motor de backtest, consulte §9.

Próximos pasos recomendados:

  • En un cuaderno, cree un HistoryPanel basado en su propio universo de acciones, intente apilar múltiples factores (medias móviles, volatilidad, valoración, etc.) y trace gráficos alineados con los precios;

  • Basándose en la lectura de la [HistoryPanel referencia de API] (…/api/HistoryPanel.rst), diseñe un flujo de trabajo para su tema de investigación: “datos → factores (HP) → solidificación de reglas (Estrategia) → backtest (Operator)”.

  • Si está considerando agregar un contenedor de API de “evaluación de factores” por separado, puede leer la nota de diseño HistoryPanel y la capa opcional FactorResearch.