quantlab.api.analyze_factors#
- quantlab.api.analyze_factors(factors, returns=None, *, prices=None, price='open', span=None, delay=1, quantiles=5, plot=True, columns=None)[source]#
Report how well each factor orders symbols by their forward return.
Every factor column is paired with the forward returns, and the library’s factor report (
quantlab.analysis.factor_report) measures each pair on the bars and symbols the two share: the per-bar IC (Pearson and Spearman rank correlation across symbols) and its statistics, the mean forward return of each ofquantilesequal-count buckets by factor value, the top-minus-bottom spread, turnover and rank autocorrelation. With two or more factor columns it also measures their correlation.Exactly one source of forward returns is given:
returns, the caller’s own, orprices, from which they are computed exactly asforward_returns(prices, price=price, span=span, delay=delay)computes them.- Parameters:
- factorspandas.DataFrame, polars.DataFrame or xarray.Dataset
Factor values in long form:
timestamp,symboland one numeric column per factor, such ascompute_factorsreturns. Its library is the oneFactorReport.summary()returns (pandas for a panel).- returnsDataFrame, optional
Forward returns, long (
timestamp,symboland one value column, whose name becomes the fret’s) or wide (timestamps in atimestampcolumn or a pandasDatetimeIndex, one column per symbol; the fret is named"returns").- pricesDataFrame, optional
Bars in long form holding the
pricecolumn.- pricestr, default “open”
With
prices, the column the returns are computed on.- spanint, optional
Bars the forward returns span, at least 1. With
pricesthe holding period, 1 when not given. Withreturnsit is required: the horizon the returns were computed over, which only the caller knows. Cumulative bucket returns compound the per-bar rate(1 + r) ** (1 / span) - 1, and the IC’s Newey-West t-statistic allows for the overlap of multi-bar returns.- delayint, default 1
With
prices, the bars between the signal bar and the entry bar.- quantilesint, default 5
Buckets per bar, at least 2 and at most the largest cross-section (the most symbols on one bar with both a factor value and a return). A bar with fewer symbols than buckets has no bucket returns.
- plotbool, default True
Draw one figure per factor into
FactorReport.figures. Drawing dominates the cost of a large report (all of Alpha158 on 50 bars x 300 symbols: about 19 s with figures, 1.4 s without); withFalsethe figures are empty andsavedraws them only then.- columnsmapping of str to str, optional
Renames caller columns onto the canonical names,
{"date": "timestamp"}, applied to each input where it has the column.
- Returns:
- FactorReport
summary()(the headline metrics per factor, a frame of thefactors’ library),figures,save(dir)andraw, the library’sFactorAnalysiswith every metric and table.
- Raises:
- ValueError
If both or neither of
returnsandpricesare given,returnscomes withoutspan,priceordelayis set withreturns,spanis below 1,delaybelow 0 orquantilesbelow 2 or above the largest cross-section (named), thepricecolumn is missing, the factors have no factor column or a non-numeric one,columnsnames a column no input has, the returns are long with other than one value column, a(timestamp, symbol)pair repeats, the factors and returns differ in bar spacing, or they share no bar and symbol (naming both time zones when they differ).- TypeError
If an input is not a pandas or polars DataFrame (or an xarray panel),
priceis not a string,span,delayorquantilesis not an int, orplotis not a bool.
Examples
>>> import numpy as np >>> import pandas as pd >>> import quantlab.api as qa >>> rng = np.random.default_rng(0) >>> bars = pd.bdate_range("2024-01-01", periods=60) >>> symbols = [f"S{i}" for i in range(10)] >>> close = 100 * np.exp(rng.normal(0, 0.01, (60, 10)).cumsum(axis=0)) >>> prices = pd.DataFrame({ ... "timestamp": np.repeat(bars, 10), "symbol": symbols * 60, ... "open": (close * 0.999).ravel(), "close": close.ravel(), ... }) >>> factors = prices.assign( ... momentum=prices.groupby("symbol")["close"].pct_change(5), ... reversal=-prices.groupby("symbol")["close"].pct_change(1), ... )[["timestamp", "symbol", "momentum", "reversal"]] >>> report = qa.analyze_factors(factors, prices=prices, span=5) >>> report FactorReport(2 pairs: momentum__ret_5, reversal__ret_5) >>> summary = report.summary() >>> list(summary.columns) ['factor', 'fret', 'ic', 'rank_ic', 'icir', 'rank_icir', 'long_short_return', 'turnover'] >>> summary.sort_values("rank_ic", ascending=False)[["factor", "rank_ic", "turnover"]].round(4) factor rank_ic turnover 0 momentum 0.0474 0.3958 1 reversal 0.0001 0.7885
The caller’s own forward returns, here in wide form, with three buckets and no figures:
>>> returns = qa.forward_returns(prices, span=5).pivot( ... index="timestamp", columns="symbol", values="ret_5") >>> report = qa.analyze_factors(factors, returns, span=5, quantiles=3, plot=False) >>> report.raw.pairs["momentum__returns"].quantiles, report.figures (3, {})