quantlab.factor.kunquant.FactorKunQuant#

class quantlab.factor.kunquant.FactorKunQuant(config)[source]#

Bases: Factor

Factor backend that compiles a KunQuant op graph to native code.

A subclass describes its factor as a KunQuant graph in _get_factor_func: Input nodes named after config.data_columns, operator nodes, and one Output per factor name. The same graph is compiled on demand in two layouts, TS for compute() (a whole date range in one call) and STREAM for cal_stream() (one bar at a time), so a factor validated in a backtest runs unchanged on live data. config.mode says which of the two the object is used in.

Compilation needs a working C++ compiler and dominates run time on small panels; a graph is compiled once and then reused from the process or the disk cache (quantlab.factor.kunquant_compile). Pinning config.factor_names to the columns you need keeps the compiled graph small. In batch mode the number of symbols must be a multiple of the SIMD block width KunQuant uses on the host, that is, the number of values the CPU processes in one vector instruction.

Parameters:
configFactorConfig

The factor config, including mode ("batch" or "stream"), data_columns and njobs.

Examples

A factor measuring how far the close is above its 5-bar average:

class MaDeviation(FactorKunQuant):
    def _get_factor_names(self):
        return ("ma_dev_5",)

    def _get_factor_func(self):
        builder = Builder()
        with builder:
            close = Input("close")
            dev = op.Div(close, op.WindowedAvg(close, 5))
            Output(op.SubConst(dev, 1.0), "ma_dev_5")
        return Function(builder.ops)
PEAK_COPIES = 1#

A compiled graph holds its float32 outputs and little else: Alpha101 and Alpha158 on us3000 (3103 bars x 7625 symbols) peaked at 0.43-0.50 of their inputs and outputs at 8 bytes (2026-10-09), so 1 leaves about a factor of two. See Factor.cell_bytes.

SYMBOL_BLOCK_DARWIN = 8#

Symbol count a batch run is padded to a multiple of on macOS. KunQuant’s compiled loops process symbols in fixed-size SIMD blocks and cannot handle a remainder: four values per block on Apple silicon, eight with AVX2 on an Intel Mac; eight covers both.

cal_stream(data, timestamp, symbols)[source]#

Advance the streaming graph by one bar and return that bar’s outputs.

The stream is initialized on first use.

Parameters:
datadict[str, np.ndarray]

Column name to a 1-D array of length num_symbols for every name in config.data_columns.

timestampint

The bar’s timestamp, used as the single time coordinate.

symbolslist[str]

Symbol coordinate values, in the order the arrays are laid out.

Returns:
xr.Dataset

The (1, num_symbols) panel of this bar.

Examples

>>> for step in range(3):                  # replay three bars
...     bar = {"adjClose": adj_close[step]}  # float32, per symbol
...     row = factor.cal_stream(bar, step, symbols)
>>> row.sizes
Frozen({'timestamp': 1, 'symbol': 16})
>>> list(row.data_vars)
['rank_close', 'ma_close', 'ma_rank']
compute(start, end)[source]#

Compute start to end in batch mode; see Factor.compute.

Raises:
ValueError

If config.mode is not "batch", or start is after end.

Examples

>>> dict(factor.compute("2024-02-01", "2024-02-10").sizes)
{'timestamp': 10, 'symbol': 16}
config_cls#

The config class from_config rebuilds this factor with.

alias of FactorConfig

copy()[source]#

Return a copy without the compiled library or stream state.

See Factor.copy. The compiled batch library, the stream context and its buffer handles belong to this object’s own graph and are not carried over; the copy compiles again on its first compute().

Examples

>>> factor.copy()._lib is None
True
init_stream()[source]#

Compile the graph in the streaming layout and bind its buffers.

Creates a StreamContext sized to num_symbols and caches a buffer handle for every input column and every factor name, so cal_stream does not look handles up by name on the hot path. Every name in config.data_columns must be consumed by a reachable Output: KunQuant prunes unused inputs and the handle lookup for a pruned one fails.

Returns:
Self

self, for chaining.

Examples

>>> factor.init_stream() is factor    # config.mode == "stream"
True
>>> sorted(factor._buffer_name_to_id)  # one input, three outputs
['adjClose', 'ma_close', 'ma_rank', 'rank_close']
keep_compiled()[source]#

Hold the batch library for every compute inside the block.

Outside the block each call builds its graph again and takes the library from compile_graphs, which compiles only the first time (in this process or, through the disk cache, any other); inside it the first call’s library is kept until the block exits, so later calls do not even build the graph. See Factor.keep_compiled.

Examples

>>> with factor.keep_compiled():   # builds the graph once, not twice
...     january = factor.compute("2024-01-01", "2024-01-31")
...     february = factor.compute("2024-02-01", "2024-02-29")
>>> factor._lib is None
True
property num_symbols#

Number of symbols the streaming graph runs over.

This is the symbol list pinned on the dataset config, since a stream loads no panel.

Raises:
ValueError

If config.mode is not "stream".

Examples

>>> factor.num_symbols        # stream config pinning 16 symbols
16
property symbols#

Symbols the streaming graph runs over, in axis order.

This is the symbol list pinned on the dataset config. In batch mode the symbols are those of the requested panel instead.

Raises:
ValueError

If config.mode is not "stream".

Examples

>>> factor.symbols[:3]
['AAPL', 'MSFT', 'NVDA']