quantlab.analysis.factor_report.FactorAnalysis#

class quantlab.analysis.factor_report.FactorAnalysis(pairs, figures=<factory>, config=<factory>, correlation=None, correlation_figure=None)[source]#

Bases: object

The result of Factor.analyze: one PairAnalysis per pair.

Attributes:
pairsdict[str, PairAnalysis]

Keyed by "<factor>__<fret>".

figuresdict[str, matplotlib.figure.Figure]

The composite figure of each pair, same keys. The figures are built without pyplot, so they are not registered with any GUI and need no closing; fig.savefig(path) writes one.

configdict

{"factor": factor config, "frets": [fret configs]}, the dicts rebuild rebuilds each object from.

correlationFactorCorrelation or None

The correlation between the analyzed factor variables, when there are two or more; None otherwise.

correlation_figurematplotlib.figure.Figure or None

The figure of correlation, held when no output_dir was given.

decay_of(factor_name)[source]#

Return factor_name’s rows of ic_decay_table(), or None with one fret.

The pair figures draw these rows as their IC-decay panel.

Examples

>>> analysis.decay_of("signal") is None      # one fret
True
has_decay()[source]#

Whether two or more frets were analyzed, so an IC decay is drawn.

Examples

>>> analysis.has_decay()      # one fret
False
ic_decay_table()[source]#

Return every pair’s mean IC with its horizon, ordered by factor then horizon.

Columns are factor, fret, horizon, ic_mean, ic_nw_t_stat and ci_low/ci_high, the 95% interval ic_mean ± 1.96 * se with the Newey-West standard error se = |ic_mean / ic_nw_t_stat|. Read down one factor’s rows to see how fast its signal decays as the forward return lengthens.

Examples

>>> analysis.ic_decay_table()[["factor", "fret", "horizon"]]
   factor   fret  horizon
0  signal  ret_1        1
ic_table()[source]#

Return every pair’s IC series as one tidy table.

Columns are timestamp, factor, fret, ic (rank) and pearson_ic.

Examples

>>> analysis.ic_table().head(2).round({"ic": 4, "pearson_ic": 4})
   timestamp  factor   fret      ic  pearson_ic
0 2024-01-01  signal  ret_1  0.1308      0.1260
1 2024-01-02  signal  ret_1 -0.1353     -0.0991
monthly_ic_table()[source]#

Return every pair’s monthly mean IC, tidy.

Columns are month (month end), factor, fret and ic.

Examples

>>> analysis.monthly_ic_table().round({"ic": 4})
       month  factor   fret      ic
0 2024-01-31  signal  ret_1  0.2159
1 2024-02-29  signal  ret_1  0.1772
2 2024-03-31  signal  ret_1  0.3103
3 2024-04-30  signal  ret_1  0.2655
quantile_returns_table()[source]#

Return every pair’s mean return per bucket and period, tidy.

Columns are timestamp, factor, fret, quantile and mean_return.

Examples

>>> list(analysis.quantile_returns_table().columns)
['timestamp', 'factor', 'fret', 'quantile', 'mean_return']
save(output_dir, workers=None, figures=True)[source]#

Write the analysis to output_dir, creating it if needed.

Files written: summary.json (every scalar metric, per pair), summary.csv, ic.csv, monthly_ic.csv, quantile_returns.csv, turnover.csv, one <factor>__<fret>.png per pair and config.json (see config). With a correlation, also factor_correlation.csv (the mean matrix in cluster order), factor_correlation_pairs.csv (pairs_table()), factor_clusters.csv (cluster_table()) and factor_correlation.png, and summary.json holds a "correlation" entry (FactorCorrelation.summary). With two or more frets, also ic_decay.csv (ic_decay_table()), and every pair figure includes an IC-decay panel. Floats that are NaN or infinite are written to JSON as null. Figures held in figures are saved as they are; when none is held, every pair is drawn from its metrics and saved, on workers processes, without being kept. With figures=False no PNG is written, neither a pair’s nor the correlation’s.

Parameters:
output_dirstr or pathlib.Path

Directory to write into.

workersint, optional

Processes that draw the figures; None uses every CPU the process may run on (os.process_cpu_count).

figuresbool, default True

Write the figures; False writes the tables and JSON files only.

Returns:
pathlib.Path

output_dir.

Examples

>>> out = analysis.save("report")
>>> sorted(p.name for p in out.iterdir())
['config.json', 'ic.csv', 'monthly_ic.csv', 'quantile_returns.csv', 'signal__ret_1.png', 'summary.csv', 'summary.json', 'turnover.csv']
>>> sorted(p.name for p in analysis.save("tables", figures=False).iterdir())
['config.json', 'ic.csv', 'monthly_ic.csv', 'quantile_returns.csv', 'summary.csv', 'summary.json', 'turnover.csv']
summary()[source]#

Return the headline metrics, one row per pair, to sort and filter factors by.

Columns:

  • factor, fret: the pair;

  • ic: mean per-period Pearson IC (pearson_ic_mean);

  • rank_ic: mean per-period Spearman IC (ic_mean);

  • icir, rank_icir: each mean over its standard deviation (pearson_ir, ir);

  • long_short_return: mean per-period forward return of the top quantile minus the bottom one (mean_spread), over the fret’s horizon;

  • turnover: mean per-period turnover of the top and bottom quantiles, averaged (mean_turnover_top, mean_turnover_bottom).

Every value is one of summary_table()’s, which holds the full set of scalar metrics.

Returns:
pandas.DataFrame

One row per pair, in the order of pairs.

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=100),
...           "symbol": [f"S{i}" for i in range(20)]}
>>> raw = rng.normal(size=(100, 20))
>>> signal = xr.DataArray(raw, coords=coords, dims=("timestamp", "symbol"))
>>> ret = xr.DataArray(0.01 * (0.3 * raw + rng.normal(size=(100, 20))),
...                    coords=coords, dims=("timestamp", "symbol"))
>>> analysis = FactorAnalyzer(plot=False).analyze_panels(
...     signal.to_dataset(name="signal"), [Fret(ret.to_dataset(name="ret_1"))]
... )
>>> analysis.summary()[["factor", "fret", "ic", "rank_ic", "turnover"]].round(4)
   factor   fret    ic  rank_ic  turnover
0  signal  ret_1  0.26   0.2384    0.8068
summary_table()[source]#

Return the scalar metrics, one row per pair.

Examples

>>> table = analysis.summary_table()
>>> table[["factor", "fret", "ic_mean", "mean_spread"]].round(4)
   factor   fret  ic_mean  mean_spread
0  signal  ret_1   0.2384       0.0069
turnover_table()[source]#

Return every pair’s bucket turnover and rank autocorrelation, tidy.

Columns are timestamp, factor, fret, quantile and turnover; the factor’s lag-1 rank autocorrelation is repeated on each bucket row as rank_autocorrelation, and every further lag k as rank_autocorrelation_lag<k>.

Examples

>>> list(analysis.turnover_table().columns)
['timestamp', 'factor', 'fret', 'quantile', 'turnover', 'rank_autocorrelation', 'rank_autocorrelation_lag5', 'rank_autocorrelation_lag10', 'rank_autocorrelation_lag20']