"""Minimal custom DataHandler — how to extend TACHandler for a new feature family. `TACHandler(DataHandlerLP)` already routes lake bars + ta-lib features via `LakeFeatureProvider` (see tac_qlib/contrib/data/handler.py). To add a NEW feature family (computed once, persisted into the lake features parquet — see examples/persist_sp_features.py), you only need to: 1. persist extra columns into features/market=US/timeframe=1d/symbol=*.parquet 2. list them in `feature_fields` (they are prefixed with `$` and de-duped) A subclass is only needed when the feature must be computed *inside* the qlib pipeline (e.g. as an extra processor). This file sketches that pattern. Reference handler structure (from tac_qlib/contrib/data/handler.py): class TACHandler(DataHandlerLP): def __init__(self, instruments, start_time, end_time, freq, fit_start_time=None, fit_end_time=None, feature_fields=None, label=None, lake_root=None, market="US", infer_processors=None, learn_processors=None, **kwargs): loader = QlibDataLoader(configured=(feature_fields or self.DEFAULT_FIELDS), freq=freq) super().__init__(instruments, start_time, end_time, freq=freq, data_loader=loader, infer_processors=infer_processors or DEFAULT_INFER_PROCESSORS, learn_processors=learn_processors or DEFAULT_LEARN_PROCESSORS, fit_start_time=fit_start_time, fit_end_time=fit_end_time, process_type=DataHandlerLP.PTYPE_A, **kwargs) """ from __future__ import annotations from typing import Any, List, Optional from tac_qlib.contrib.data.handler import DEFAULT_INFER_PROCESSORS, DEFAULT_LEARN_PROCESSORS, TACHandler class CustomFeaturesHandler(TACHandler): """TACHandler variant that also loads the lake feature columns passed in. Usage from YAML — only the handler kwargs change: handler: class: CustomFeaturesHandler module_path: tac_qlib.contrib.data.handler # after adding this class there kwargs: instruments: AAPL,MSFT,QQQ start_time: 2026-03-01 end_time: 2026-08-06 freq: day lake_root: "{{ LAKE }}" market: US feature_fields: "$close,sp_ou_alpha,sp_hurst_exponent" label: "Ref($close,-6)/Ref($close,-1)-1" """ def __init__( self, feature_fields: Optional[List[str]] = None, infer_processors: Optional[List[Any]] = None, learn_processors: Optional[List[Any]] = None, **kwargs: Any, ): # `feature_fields` are passed through with the leading `$` stripped by # TACHandler; infer/learn default to the lake-tuned processor stacks. super().__init__( feature_fields=feature_fields, infer_processors=infer_processors or DEFAULT_INFER_PROCESSORS, learn_processors=learn_processors or DEFAULT_LEARN_PROCESSORS, **kwargs, )