quantlab.analysis.factor_report.FactorAnalyzer#

class quantlab.analysis.factor_report.FactorAnalyzer(quantiles=5, rolling_window=22, plot=True, workers=None, chunk_size=32, correlation_threshold=0.7, autocorrelation_lags=(1, 5, 10, 20))[source]#

Bases: object

Compute alphalens-style metrics of factor variables against frets.

The metrics are computed with polars: the aligned panels become one long (timestamp, symbol) frame per chunk of factor variables, every per-period statistic of every variable in the chunk is one lazy plan, and the plan is collected once. Only the small per-period series are then finished in pandas.

Parameters:
quantilesint, default 5

Number of equal-count buckets per timestamp. Timestamps with fewer usable symbols than buckets get no bucket returns.

rolling_windowint, default 22

Window, in periods, of the rolling mean IC drawn in the figure.

plotbool, default True

Draw figures. With an output_dir they are drawn on workers processes straight to PNG files and not kept; without one they are held in FactorAnalysis.figures. False draws none: an output_dir then gets the tables and JSON files only.

workersint, optional

Processes that draw the figures of a saved report; None uses every CPU the process may run on (os.process_cpu_count, which follows taskset and numactl).

chunk_sizeint, default 32

Factor variables per lazy plan. Each plan holds chunk_size + 3 float columns of timestamps * symbols rows in memory.

correlation_thresholdfloat, default 0.7

|correlation| at which the factor-correlation clusters are cut; see quantlab.analysis.factor_correlation.

autocorrelation_lagssequence of int, default (1, 5, 10, 20)

Lags, in periods, of the factor rank autocorrelation. Lag 1 is always included.

Examples

>>> analyzer = FactorAnalyzer(quantiles=5)
>>> pair = analyzer.analyze_pair(signal, ret, "signal", "ret_1")
>>> round(pair.summary["ic_mean"], 4), pair.mean_quantile_returns.round(4).tolist()
(0.2384, [-0.0037, -0.0013, -0.0005, 0.0018, 0.0032])
analyze_many(features, labels, horizon=1)[source]#

Compute every metric of every factor variable against every fret.

Parameters:
features, labelsxarray.Dataset

Aligned (timestamp, symbol) panels: the factor variables and the forward-return variables. Only cells finite in both a factor and a fret are used for that pair.

horizonint, default 1

Bars the forward returns span.

Returns:
list of PairAnalysis

One per (factor variable, fret variable), frets outermost.

Examples

>>> pairs = FactorAnalyzer().analyze_many(signals, rets, horizon=1)
>>> [pair.key for pair in pairs]
['signal__ret_1', 'noise__ret_1']
analyze_pair(factor, fret, factor_name, fret_name, horizon=1)[source]#

Compute every metric of one aligned factor/fret pair.

Parameters:
factor, fretxarray.DataArray

Aligned (timestamp, symbol) panels. Only cells finite in both are used.

factor_name, fret_namestr

Names recorded in the result.

horizonint, default 1

Bars the forward return spans.

Returns:
PairAnalysis

The metrics.

Examples

>>> pair = FactorAnalyzer().analyze_pair(signal, ret, "signal", "ret_1")
>>> pair.key, pair.quantile_returns.shape
('signal__ret_1', (100, 5))
analyze_panels(features, frets, *, config=None, output_dir=None, factor_label='factors')[source]#

Analyze every variable of features against every fret panel.

The panel-level core of run, for panels that come from no Factor object: each fret panel is checked for the features’ bar spacing, inner-joined with features and analyzed; the factor correlation, the figures and output_dir are handled as in run.

Parameters:
featuresxarray.Dataset

The factor variables on (timestamp, symbol); every variable is analyzed.

fretssequence of Fret

The forward-return panels, each with its horizon and the name error messages give it.

configdict, optional

Recorded as FactorAnalysis.config; empty when None.

output_dirstr or pathlib.Path, optional

When given, FactorAnalysis.save writes the results there and the figures are not kept in memory.

factor_labelstr, default “factors”

Names the features in error messages (run passes the factor’s class name).

Returns:
FactorAnalysis

The metrics, figures and config.

Raises:
ValueError

If no fret is given, a fret panel’s bar spacing differs from the features’, a fret panel shares no cell with the features, or two pairs share a name.

Examples

>>> import numpy as np
>>> import pandas as pd
>>> import xarray as xr
>>> from quantlab.analysis.factor_report import FactorAnalyzer, Fret
>>> rng = np.random.default_rng(0)
>>> coords = {"timestamp": pd.date_range("2024-01-01", periods=50),
...           "symbol": [f"S{i}" for i in range(10)]}
>>> raw = rng.normal(size=(50, 10))
>>> features = xr.Dataset({"signal": (("timestamp", "symbol"), raw)}, coords=coords)
>>> rets = xr.Dataset(
...     {"ret_2": (("timestamp", "symbol"), 0.3 * raw + rng.normal(size=(50, 10)))},
...     coords=coords,
... )
>>> analysis = FactorAnalyzer(plot=False).analyze_panels(
...     features, [Fret(rets, horizon=2)]
... )
>>> list(analysis.pairs), analysis.pairs["signal__ret_2"].horizon, analysis.config
(['signal__ret_2'], 2, {})
static check_frequency(features, labels, factor_label='factor', fret_label='fret')[source]#

Refuse panels whose most common bar spacing differs.

Parameters:
features, labelsxarray.Dataset

The factor and forward-return panels.

factor_label, fret_labelstr

Names used in the error message.

Returns:
numpy.timedelta64

The shared spacing.

Raises:
ValueError

If the spacings differ.

Examples

>>> FactorAnalyzer.check_frequency(daily, weekly, "signal", "ret")
Traceback (most recent call last):
...
ValueError: bar spacing differs: signal has 1 days 00:00:00 bars, ret has 7 days 00:00:00 bars; resample one panel to the other's frequency first
run(factor, frets, features, labels, factor_names=None, output_dir=None)[source]#

Analyze features against every fret’s labels; see Factor.analyze.

Parameters:
factorFactor

The analyzed factor; its names, class and config are recorded.

fretssequence of Forward

Forward-return labels, in the order of labels; each one’s span_bars() is its horizon.

featuresxr.Dataset

The factor’s panel from compute or read.

labelssequence of xr.Dataset

Each fret’s panel from compute or read.

factor_namessequence of str, optional

Factor variables to analyze; all of get_factor_names() when None.

output_dirstr or pathlib.Path, optional

When given, FactorAnalysis.save writes the results there and the figures are not kept in memory.

Returns:
FactorAnalysis

The metrics, figures and configs.

Raises:
ValueError

If no fret is given, a factor name is unknown, a fret panel’s bar spacing differs from the factor’s, the aligned panel is empty, or two pairs share a name.

Examples

>>> window = ("2024-02-01", "2024-02-29")
>>> analysis = FactorAnalyzer(quantiles=4).run(
...     factor, [fwd],
...     features=factor.compute(*window),
...     labels=[fwd.compute(*window)],
... )
>>> list(analysis.pairs)
['momentum_5__ret_1']