3. Verwenden Sie HistoryPanel, um historische Daten zu bearbeiten und zu analysieren

Nachdem wir die ersten beiden Kapitel „Erste Schritte“ und „Finanzdaten beschaffen und verwalten“ abgeschlossen haben, können wir bereits:

  • Konfigurieren Sie die Datenquelle lokal und laden Sie die erforderlichen historischen Daten herunter;

  • Verwenden Sie „get_history_data()“ oder „get_history_panel()“, um grundlegende Marktdaten wie Preise und Handelsvolumen zu erhalten.

Darauf aufbauend wird in diesem Kapitel anhand eines vollständigen kleinen Beispiels die Verwendung von „HistoryPanel“ demonstriert:

  • Einheitliche Verwaltung historischer Daten für mehrere Instrumente und mehrere Indikatoren;

  • Berechnen Sie einfache Faktoren wie Rendite und Volatilität.

  • Technische Indikatoren der Overlay-K-Linie;

  • Identifizieren Sie einige Candlestick-Muster.

  • Verwenden Sie „where“, um eine Bool-Maske mit der gleichen Form wie die Daten zu generieren, als Vorbereitung für nachfolgende Forschungs-APIs, die „mask=“;

  • Verwenden Sie Spaltenattribute (z. B. „hp.close“), Vergleichsoperatoren (die ein boolesches „numpy“-Array zurückgeben, das mit „where“ verkettet werden kann) und „hp.loc“ (filtert nur entlang der Zeitachse, äquivalent zu „hp[:, :, key]“).

Eine ausführlichere API-Beschreibung finden Sie in der HistoryPanel API-Referenz (einschließlich der speziellen Abschnitte zu „Spaltenattributzugriff, Vergleiche und „loc““ und zu „where“).


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

Angenommen, wir haben bereits die täglichen Daten des letzten Jahres für den CSI 300-Index („000300.SH“) heruntergeladen. Wir können ein „HistoryPanel“ direkt über „qt.get_kline()“ oder „qt.get_history_data()“ erhalten:

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)

An dieser Stelle:

  • „hp.shape“ ist ein „(L, R, C)“-Tupel, entsprechend Anzahl der Bestände × Zeitlänge × Anzahl der Datentypen;

  • hp.shares ist normalerweise ['000300.SH'];

  • Standardmäßig enthält „hp.htypes“ „[‚open‘, ‚high‘, ‚low‘, ‚close‘, ‚vol‘]“ usw.


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

„HistoryPanel“ verfügt über integrierte Schnittstellen zur Berechnung von Renditen und Volatilität für Preisreihen:

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

Eine typische Verwendung ist:

  • Behandeln Sie „ret“ oder „vol“ als Faktorwertmatrix (Zeit × Bestände);

  • Es kann direkt in die Backtesting-Engine eingespeist oder zur Aktienauswahl, Bewertung usw. verwendet werden.


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

Wenn „HistoryPanel“ OHLC-Daten enthält, können Sie über „hp.kline“ auf eine Reihe häufig verwendeter technischer Indikatoren zugreifen:

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

Hier:

  • „hp.kline.sma(…)“ hängt eine Spalte an „htypes“ an, z. B. „sma_20“;

  • „hp.kline.macd(…)“ fügt drei Spalten hinzu: „macd_xxx“, „macd_signal_xxx“ und „macd_hist_xxx“;

  • Verwenden Sie abschließend „to_share_frame(share)“, um einen standardmäßigen 2D-„DataFrame“ zu erhalten, der gemeinsam mit anderen Datenquellen direkt analysiert werden kann.

3.1 Inplace: Spalten an Ort und Stelle erweitern (Kopieren reduzieren)

Standardmäßig gibt „kline.*“ ein neues „HistoryPanel“ zurück (ohne das ursprüngliche Objekt zu verändern). Wenn Sie mehrere Spalten kontinuierlich an dasselbe Panel anhängen und Zwischenobjekte reduzieren möchten, können Sie „inplace=True“ verwenden:

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

Tipp: Wenn ein neuer Spaltenname mit einem vorhandenen Spaltennamen in Konflikt steht, löst „kline.*“ einen englischen „ValueError“ aus, um ein stilles Überschreiben zu verhindern.

3.2 Erster Einstiegspunkt: Research_preset (einzeilige Spaltenverkettung + direkte Darstellung)

Wenn Sie „ein anständig aussehendes Diagramm erhalten möchten, sobald Sie die Daten haben“, wird empfohlen, „research_preset“ direkt zu verwenden, um den offiziellen voreingestellten Satz häufig verwendeter Spalten zu generieren (die Voreinstellung führt nur explizite Anhänge durch und verbirgt keine Berechnungen in „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)

Wenn im Eingabefeld die für die Voreinstellung erforderlichen Eingabespalten fehlen, wird ein englischer „ValueError“ ausgegeben; Die Nachricht enthält eine Liste der fehlenden Spalten und Vorschläge zum Ausfüllen.


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

In der praktischen Forschung müssen Sie häufig mehrere neue Faktoren aus vorhandenen Spalten ableiten, z. B. Spreads, Verhältnisse, skalierte normalisierte Werte usw. „HistoryPanel.assign()“ bietet eine DSL zum Ableiten mehrerer Spalten auf einmal:

# 假设 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:])
  • Die Schlüsselwortargumentnamen von „assign“ sind die neuen Spaltennamen (htype); Die Werte können aufrufbare Werte sein, die den aktuellen „HistoryPanel“ annehmen, oder Arrays/Skalare, die an „(M,L)“ gesendet werden können;

  • Innerhalb desselben Aufrufs können spätere Spalten von Spalten abhängen, die gerade zuvor hinzugefügt wurden, zum Beispiel:

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

Standardmäßig ist „inplace=False“, was ein neues Panel zurückgibt, ohne das ursprüngliche Objekt zu ändern; Wenn Sie die im Originalbereich vorhandenen Spalten erweitern möchten, können Sie Folgendes verwenden:

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

Tipp: Wie hp['col'] = ... verwendet assign die Semantik „Überschreiben“ für vorhandene Spaltennamen; Der Aufruf von „assign“ in einem leeren Panel löst einen englischen „ValueError“ aus.


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

Im Multi-Asset-Research gibt es zwei häufige Arten von Standardisierungsanforderungen:

  • Querschnitt: Legen Sie einen Handelstag fest und standardisieren oder ordnen Sie Faktorwerte für alle Aktien;

  • Zeitreihen (rollend): Fixieren Sie einen Bestand und verwenden Sie ein rollierendes Fenster, um seine eigene historische Reihe zu standardisieren.

„HistoryPanel.rank()“ und „HistoryPanel.zscore()“ stellen die grundlegendsten Transformationen dieser beiden Typen bereit (nur Transformationen, keine Regressionsinferenz):

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

Hinweis: „zscore(method=‘cs‘)“ und „zscore(method=‘ts‘)“ haben eine völlig unterschiedliche Semantik. Stellen Sie sicher, dass Sie „Methode“ explizit angeben (und „Fenster“ im Fall von „ts“ angeben), um eine Verwechslung von „Querschnittsstandardisierung“ mit „Zeitreihenstandardisierung“ zu vermeiden.

5.5 Ausrichtung und Resampling (align_to / resample): Vermeiden Sie eine stille Zeilenfehlausrichtung

Wenn Sie also zwei „HistoryPanel“ haben und elementweise Operationen ausführen möchten (z. B. „hp_factor / hp_price“ oder das Subtrahieren zweier Panels), müssen Sie besonders darauf achten:

  • HistoryPanel **richtet zwei Panels innerhalb von Operatoren nicht automatisch aus;

  • Wenn „shares“ / „hdates“ inkonsistent sind, kann das Ausführen von NumPy-Operationen direkt auf „.values“ leicht zu Ergebnissen führen, die „zu berechnen scheinen, aber tatsächlich durch Zeilen falsch ausgerichtet sind“.

Daher wird empfohlen, einem festen Arbeitsablauf zu folgen: Zuerst ausrichten, dann berechnen.

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

=Wenn Sie tägliche Daten in Daten mit geringerer Häufigkeit umwandeln müssen, z. B. wöchentlich/monatlich, können Sie „resample()“ verwenden und explizit die Aggregationsregel für jede Spalte angeben (um unklare Semantik zu vermeiden):

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

Hinweis: „resample()“ erfordert „agg“, um alle „htypes“ abzudecken. Für benutzerdefinierte Faktorspalten, die nicht OHLCV sind, müssen Sie die Aggregationsmethode selbst festlegen (z. B. „last“ / „mean“) und sie in „agg“ einschließen.

5.6 Verwenden Sie eine (M, L)-Maske, um Plots (Ereignistag/Signaltag) hervorzuheben.

Bei der Faktorforschung erhalten Sie häufig eine boolesche 2D-Matrix „mask_ml“, die angibt, „an welchen Daten jede Aktie die Bedingung erfüllt“ (z. B. Ereignisdaten, Signaldaten, handelbares Universum).

HistoryPanel.plot() mit highlight={'condition': mask_ml} unterstützt die direkte Zuordnung zum Diagramm, um Punkte hervorzuheben:

  • Wenn „mask_ml.shape == (M_plot, L)“, wobei „M_plot“ die Anzahl der aktuell von „plot(shares=…)“ ausgewählten Aktien ist, entspricht dies der Plot-Aktienreihenfolge;

  • Wenn „mask_ml.shape == (M_all, L)“, wobei „M_all == len(hp.shares)“, dann extrahieren Sie die aktuelle Plotteilmenge nach Freigabename (um eine stille Zeilenfehlausrichtung zu vermeiden);

  • Bei „layout=‘overlay“ werden Hervorhebungen standardmäßig als primär-nur angezeigt (im Einklang mit der bestehenden Hervorhebungssemantik von 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 (Hintergrund: in diesem Abschnitt nicht implementiert)

In der empirischen Ökonometrie finden Sie möglicherweise robuste Newey-West (HAC)-Standardfehler und zweistufige Fama-MacBeth-Regressionen. Sie werden hauptsächlich für Regressionsinferenz (Standardfehlerkorrektur, Signifikanztests für Risikoprämien) verwendet und sind keine „grundlegenden Transformationen“ wie „HistoryPanel.zscore()“. Wenn Sie empirische Arbeit in Veröffentlichungsqualität benötigen, wird empfohlen, „HistoryPanel“ in einen „DataFrame“ zu exportieren und dann Tools wie „statsmodels“ zu verwenden, um den Inferenz-Workflow abzuschließen.


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

Wenn Sie bereits mit „qteasy.tafuncs“ ​​oder ta-lib vertraut sind, können Sie „HistoryPanel.apply_ta()“ für einen einheitlichen Wrapper verwenden:

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

Dadurch wird vermieden, dass „tafuncs.sma“ manuell in einer Schleife aufgerufen wird. „HistoryPanel“ überträgt die Berechnung automatisch an alle ausgewählten Aktien.


3.7. 7. 识别蜡烛形态

Für OHLC-Daten können Sie die Candlestick-Mustererkennungsfunktionen von ta-lib auch über „HistoryPanel.candle_pattern()“ umschließen, zum Beispiel:

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

Die Signalbedeutung ist normalerweise:

  • 0: Das Muster wurde nicht erkannt;

  • Positive/negative Werte: Mustersignale in unterschiedlichen Richtungen oder mit unterschiedlichen Stärken (abhängig von der Definition der spezifischen Ta-Lib-Funktion).


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

Die Achsenreihenfolge der Panel-„Werte“ ist (Anzahl der Vermögenswerte M, Zeitlänge L, Anzahl der Indikatorspalten N), konsistent mit „hp.shape“. „hp.where(condition)“ gibt ein gleichförmiges „numpy.ndarray“ zurück, „dtype=bool“ und ändert „hp“ nicht. „cum_return“, „normalize“ und „portfolio“ unterstützen bereits „mask=hp.where(…)“ (oder ein „bool“-Array mit denselben Broadcasting-Regeln wie „where“). Informationen zur forschungsorientierten Semantik und Randfällen (Pfadunterbrechungen, ungültige Basisdaten, Portfolio-Benchmark-Beziehungen usw.) finden Sie in der API-Dokumentzeichenfolge.

Vergleichen Sie mit der vollständigen Wertetabelle (entspricht „hp.values ​​> Schwellenwert“):

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

Beschriften Sie entlang der Zeitdimension (z. B. wenn der Index einer bestimmten Spalte ein „Ereignistag“ ist, replizieren Sie ihn über alle htypes; das Folgende dient der Veranschaulichung – stellen Sie sicher, dass „cond_ml“ die Form „(M, L)“ hat):

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

Bedingungen basierend auf der Schlusskursspalte erstellen (das „Callable“ erhält den aktuellen „HistoryPanel“):

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

Zusammengesetzte Bedingungen und fehlende Werte:

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

Hinweis: Eine 2D-Bedingung mit der Form genau „(M, L)“ bedeutet „für jeden (Anlagewert, Datum) haben alle Indikatorspalten denselben booleschen Wert“. Die Bibliothek erweitert es zunächst auf „(M, L, 1)“ und sendet es dann an „(M, L, N)“. Ein 1D „(M,)“ oder „(M, 1)“ bedeutet, dass es nur je nach Vermögenswert variiert; es wird zu „(M, 1, 1)“ erweitert und dann gesendet. Ausführlichere Hinweise zu Parametern und Ausnahmen finden Sie im Abschnitt „where“ in HistoryPanel API Reference.

8.1 Kumulative Rendite und Normalisierung („cum_return“ / „normalize“)

Gibt ein neues „HistoryPanel“ zurück (ändert das ursprüngliche Panel nicht). Standardmäßig wird es anhand der analysierten „close“-Spalte berechnet. Die Namen der Ausgabespalten lauten „cumret_“ und „norm_“ (bei Koexistenz mit der Spalte „close|b“ mit angepasstem Preis können Sie weiterhin „htypes=None“ übergeben und der Spaltenname lautet weiterhin „cumret_close“). Kann zusammen mit einer Maske verwendet werden:

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)

Forschungsorientiert: Nehmen Sie einen gleichgewichteten oder gewichteten Durchschnitt bestimmter Spalten entlang der Asset-Dimension und geben Sie ein neues „HistoryPanel“ zurück („hdates“ unverändert). „mask=“ folgt den gleichen Übertragungsregeln wie „where“. Wenn keine „Gruppen“ vorhanden sind und eine „Benchmark“ angegeben ist, nimmt der Benchmark-Vermögenswert nicht an der Kombinationsberechnung teil; Es wird nur für „benchmark_output=‘tag_along“ (eine zusätzliche Zeile für die Benchmark-Kurve) oder „excess_only“ (Ausgabe „excess_“) verwendet. Kein Konto-Backtest; keine Transaktionskosten. Weitere Informationen finden Sie in der API-Dokumentzeichenfolge.

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 Spaltenattribute, Vergleiche und „loc“ (eine kurze Anmerkung zu den Unterschieden zu pandas)

  • Spaltenattribute (schreibgeschützt): Wenn ein Spaltenname ein gültiger Python-Bezeichner ist, können Sie „hp.close“ verwenden, äquivalent zu „hp[‚close‘]“; Spaltennamen mit angepasstem Preis, die „|“ und Ähnliches enthalten, müssen in eckigen Klammern stehen. Verwenden Sie für die Zuweisung konsequent „hp[‚col‘] = …“.

  • Vergleich: Ausdrücke wie „hp.close > hp.open“ und „hp > 0“ ergeben ein „numpy.ndarray“ (bool)**, kein Unter-„HistoryPanel“; Eine Vollformmaske kann als „hp.where(hp.close > 15)“ geschrieben werden. Wenn beide Seiten Panels sind, müssen „shares“ und „hdates“ übereinstimmen; Beim Vergleich zweier Spalten handelt es sich normalerweise um zwei einspaltige Unterfelder.

  • loc: hp.loc[s] ist äquivalent zu hp[:, :, s]; Es filtert nur entlang der Zeitachse (hdates). Übergeben Sie nicht die von „where“ erzeugte „(M, L, N)“-Maske an „loc“; Gitterpunktbedingungen sollten weiterhin „where“ + ein nachfolgendes „mask=“ verwenden.

Einzelheiten und Randfälle finden Sie in der HistoryPanel API-Referenz (einschließlich „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“ usw. auf „HistoryPanel“ sind forschungsorientierte Tools: praktisch zum Betrachten von Faktoren und Anzeigen grober Portfoliokurven und enthalten keine Kontosemantik, die mit formalen Backtests vereinbar ist, wie z. B. Abwicklungszyklen, Gebühren, Slippage und Analyse von Signaltypen (PT/PS/VS). Wenn die Schlussfolgerungen für den Live-Handel oder eine ernsthafte Bewertung verwendet werden sollen, sollten Sie Regeln mit der gleichen wirtschaftlichen Bedeutung in „realize()“ der Strategie schreiben und diese an „Operator“ + Backtester übergeben.

Im Folgenden verwenden wir die gleiche Idee – einfaches 20-Perioden-Momentum, um die beiden Phasen zu verbinden (erfordert, dass eine Datenquelle lokal konfiguriert und die entsprechenden Felder heruntergeladen wurden; Bestandscodes dienen nur zur Veranschaulichung).

9.1 Forschungsphase: Berechnen Sie Faktoren für ein Multi-Asset „HistoryPanel“ und werfen Sie einen kurzen Blick auf die Querschnitte

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

Hier haben Sie die Faktorspaltengenerierung, Querschnittstransformationen und grobe Aggregation auf einem 3D-Panel abgeschlossen; Wenn die Ergebnisse gut aussehen, fahren Sie mit 9.2 fort und verschieben Sie die Faktordefinition in die Strategie.

9.2 Formale Backtesting-Phase: FactorSorter + Operator

Aktienauswahlstrategien verwenden üblicherweise „FactorSorter“: „realize()“ gibt einen Skalarfaktor pro Instrument zurück (Länge = Anzahl der Aktien), und das Framework sortiert und weist dann Gewichtungen basierend auf „max_sel_count“, „sort_ascending“ usw. zu. Das folgende Momentum hat dieselbe wirtschaftliche Bedeutung wie „returns(…, periods=20)“ in 9.1, aber die Daten stammen aus der Injektion über das Strategiefenster (ausgerichtet auf Backtest-Schritte und eingeschränkt durch „use_latest_data_cycle“ usw.).

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、起止日期与回测配置

Notiz:

  • Standardmäßig ermöglicht „Operator“ die Verwendung von Zielpositionssignalen der PT-Klasse innerhalb von Gruppen; Wie „FactorSorter“ mit PT gepaart wird, sollte der Strategiedokumentation in Ihrem Projekt folgen.

  • In HP unter Verwendung der gesamten Stichprobe berechnete Ränge/Z-Scores können numerisch leicht von den Faktoren in Backtests abweichen, bei denen „jeder Schritt nur das historische Fenster sieht“; das wird erwartet. Sie sollten die Verwendung von „get_data“ innerhalb der Strategie noch einmal überprüfen, um einem Look-Ahead-Bias vorzubeugen.

Das vollständige ausführbare Skript finden Sie unter „examples/historypanel_research_to_strategy.py“ im Projektstammverzeichnis.


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

„HistoryPanel“ kann beliebige „htypes“-Spalten enthalten; Ein gängiger Ansatz für mehrere Tabellen / mehrere DataTypes ist: (1) mehrere Spalten in einem „get_history_data“-Aufruf abrufen; (2) oder zwei Panels separat abrufen und dann „align_to“ ausführen.

10.1 Mehrere Spalten auf einmal abrufen (Häufigkeit und Asset-Typ müssen konsistent sein)

Wenn Sie bereits lokal über tägliche „Close“- und „PB“-Felder (Kurs-Buchwert-Verhältnis) verfügen, können Sie direkt:

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

Wenn eine Spalte fehlt oder eine Tabelle nicht heruntergeladen wurde, löst „get_history_data“ einen Fehler aus; Bitte vervollständigen Sie zunächst die Tabellendaten über das Datenquellen-Tutorial.

10.2 Zuerst zwei Panels ausrichten, dann ableiten (verschiedene Quellen / verschiedene Züge)

„align_to“ erfordert, dass die Namen und die Reihenfolge der „htypes“ auf beiden Seiten genau übereinstimmen. Wenn Sie nur die Spalten „close“ und „pb“ separat abgerufen haben, müssen Sie sie zunächst zu einem homogenen Satz von Spalten erweitern (verwenden Sie „NaN“ als Platzhalter auf der fehlenden Seite) und dann nach der Ausrichtung „assign“ verwenden, um die gültigen Werte wieder in denselben Satz von Spaltennamen zu schreiben:

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

Das Gleiche gilt für weitere Bereiche (Branche, Marktkapitalisierung usw.); Wenn Sie sie in einem Aufruf wie „get_history_data(htypes=‘close, pb‘, …)“ abrufen können, bevorzugen Sie §10.1, um einen manuellen Homogenisierungsschritt zu vermeiden. Das Kernprinzip lautet nach wie vor: „Zuerst ausrichten, dann berechnen“, um zu vermeiden, dass NumPy Zeilen stillschweigend falsch ausrichten.

Eine ausführbare Demo zu synthetischen Daten finden Sie unter „examples/historypanel_multisource_research.py“ im Stammverzeichnis des Projekts.


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

„HistoryPanel“ verfügt nicht über integrierte Inferenz-Workflows wie Newey-West oder Fama-MacBeth; Wenn Sie Regressionen in Veröffentlichungsqualität benötigen, sollten Sie in eine 2D-Tabelle exportieren und dann Bibliotheken wie „statsmodels“ verwenden.

11.1 Aufteilung nach Asset in mehrere DataFrames (breite Tabelle)

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

11.2 Manuelles Stapeln einer Wachstumstabelle (zur einfachen täglichen Querschnittsgruppierung)

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 Eintägiges OLS-Querschnittsbeispiel (erfordert die Installation von Statistikmodellen)

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

Die oben genannten abhängigen Variablen dienen nur zur Veranschaulichung; In der Praxis sollten Sie Faktoren an Forward-Returns ausrichten und mit Aussetzungen, Survivorship Bias usw. umgehen – all dies sollte auf der ökonometrischen Ebene erfolgen und nicht als Kernverantwortung von „HistoryPanel“.

Ein Beispiel für synthetische Daten finden Sie unter „examples/historypanel_statsmodels_export.py“ im Projektstammverzeichnis.


3.12. 12. 小结与下一步

Anhand der Beispiele in diesem Kapitel können Sie Folgendes sehen:

  • „HistoryPanel“ bietet einen einheitlichen 3D-Container für historische Daten, der für die gemeinsame Forschung über mehrere Instrumente/mehrere Indikatoren hinweg geeignet ist;

  • Darüber hinaus können Sie statistische Methoden (describe/mean/std/min/max), rollierende Fenster (rolling), Renditen/Volatilität (returns/volatility), Querschnittsranking und Standardisierung (rank/zscore), kumulative Renditen und Normalisierung (cum_return/normalize), Portfolioaggregation (portfolio), Candlestick-Indikatoren (K-Linie) direkt aufrufen (kline.*), technische Indikatorüberbrückung (apply_ta), Candlestick-Mustererkennung (candle_pattern), Recherchemasken (where) sowie Spaltenattribute/Vergleiche/loc usw.;

  • Sie können bei jedem Schritt problemlos zu einem „DataFrame“ zurückwechseln, um mit pandas / sklearn / statsmodels usw. zusammenzuarbeiten;

  • Für formales Backtesting verwenden Sie „Strategy.realize“ + „Operator“; Für die Grenze und den Migrationspfad zwischen forschungsorientiertem HP und der Backtest-Engine siehe §9.

Empfohlene nächste Schritte:

  • Erstellen Sie in einem Notebook ein „HistoryPanel“ basierend auf Ihrem eigenen Aktienuniversum, versuchen Sie, mehrere Faktoren (gleitende Durchschnitte, Volatilität, Bewertung usw.) zu stapeln und Diagramme zu zeichnen, die an den Preisen ausgerichtet sind;

  • Entwerfen Sie basierend auf der Lektüre der HistoryPanel API-Referenz einen Workflow für Ihr Forschungsthema: „Daten → Faktoren (HP) → Regelverfestigung (Strategie) → Backtest (Operator)“.

  • Wenn Sie überlegen, ob Sie einen separaten API-Wrapper für die „Faktorbewertung“ hinzufügen möchten, können Sie den Designhinweis [HistoryPanel und die optionale FactorResearch-Ebene] (…/design/10-historypanel-factor-research-layer.md) lesen.