quantlab.factor.predefined.barra.BarraStyle#

class quantlab.factor.predefined.barra.BarraStyle(config)[source]#

Bases: FactorKunQuant

USE4-style exposures: standardized descriptors and style factors.

Reads the panel variables of BarraStyleParameters.panel_columns: the adjusted close, the market cap, the risk-free rate, the raw close, raw volume, cash dividend and split factor, eight SF1 fundamentals and the fiscal-year EPS and sales-per-share history. They usually come from a merge of a Sharadar price dataset, the DAILY dataset, the SF1 ART fundamentals dataset (whose balance-sheet items equal ARQ’s on every filing), the fiscal-year history dataset and a risk-free series broadcast across symbols, and the share-class firm of each symbol. On every bar t:

  • a secondary share class (firm_column naming another symbol) takes that symbol’s market cap, fundamentals and fiscal-year history before anything else is computed;

  • the estimation universe is the estimation_universe_size symbols with the largest market cap at t-1, secondary share classes left out;

  • the market return is the universe’s return, weighted by the market cap at t-1;

  • returns are in excess of the risk-free rate of the bar before (the rate of a day is published the next business day); excess is r - r_f and log_excess is log(1 + r) - log(1 + r_f);

  • LNCAP is log(marketcap[t]);

  • BETA is the slope of an exponentially weighted least-squares fit of excess on the market’s excess return over the last beta_window bars (half-life beta_half_life), and HSIGMA the weighted standard deviation of that fit’s residual;

  • RSTR is the exponentially weighted sum of log_excess over momentum_window bars ending momentum_lag bars ago (half-life momentum_half_life), the weights not normalized (Empirical Notes eq. A2, p.52); a bar without a return adds nothing;

  • DASTD is the exponentially weighted standard deviation of excess over dastd_window bars (half-life dastd_half_life);

  • CMRA is log(1 + max Z) - log(1 + min Z), Z(T) the sum of log_excess over the last T months of cmra_month_length bars, T = 1 .. cmra_months; NaN when min Z <= -1;

  • a day’s turnover is volume * close / marketcap, its dollar volume over its market cap: the share count marketcap / close and the volume are on that day’s share basis, so a split moves neither. A secondary share class’s turnover is its dollar volume over its firm’s market cap, as Sharadar gives no per-class share count. STOM, STOQ and STOA are log(L * mean turnover) over the last 1, stoq_months and stoa_months months of L = liquidity_month_length bars, which is USE4’s log(sum over a month) and log(mean over months of exp(STOM)) when every day has a turnover; a day without one is left out of the mean (our choice), and a window of zero volume only is NaN;

  • YILD is the cash dividends of the last dividend_window bars, each converted to today’s share basis through the splits since its ex-date, divided by today’s raw close;

  • fundamentals are converted to USD by fx_column where they meet the market cap. BTOP is book equity over market cap; ETOP is trailing net income to common over market cap and CETOP the same plus depreciation and amortization; with long-term debt LD (the total debt where the long-term part is not reported), MLEV is 1 + LD / market cap, BLEV 1 + LD / book equity (NaN for book equity at or below 0, our choice) and DTOA (LD + current liabilities) / assets over the non-current debt only (NaN where it or the current liabilities are not reported);

  • EGRO is the slope of a least-squares fit of the fiscal-year EPS eps_fy0 .. eps_fy<growth_years - 1> on time, each year placed at its fiscal year end in years before the latest’s (so a missing year or a moved year end keeps the spacing true), over the years known, divided by their mean absolute value; NaN with fewer than min_growth_years known years or a mean of 0. SGRO is the same with sales per share. Dividing by the mean absolute value, not the signed mean of the text, is our choice;

  • a windowed descriptor is NaN with fewer valid values in its window than its *_min_observations (liquidity_min_fraction of the window for Liquidity);

  • each descriptor has its outliers treated, then is standardized: with m and s the equally weighted mean and standard deviation of the raw descriptor over the universe, a value beyond m +- data_error_sigma * s becomes NaN and the rest are clipped to m +- clip_sigma * s (Methodology Notes §2.2, p.8); the result is standardized over the universe (market cap at t-1 weighted mean 0, equally weighted standard deviation 1; §2.3, p.9, eq. 2.4);

  • Size, Beta and Momentum are their one descriptor standardized again;

  • Residual Volatility is 0.75 DASTD + 0.15 CMRA + 0.10 HSIGMA (*_weight) over the descriptors present, standardized, orthogonalized against Beta and standardized again (Empirical Notes p.16, p.52);

  • Liquidity is 0.35 STOM + 0.35 STOQ + 0.30 STOA over the descriptors present, standardized; Dividend Yield is YILD standardized again; Book-to-Price is BTOP standardized again; Earnings Yield is 0.15 CETOP + 0.10 ETOP, Leverage 0.75 MLEV + 0.15 DTOA + 0.10 BLEV and Growth 0.20 EGRO + 0.10 SGRO, each over the descriptors present, standardized;

  • a style still missing for a symbol with a market cap on the bar is imputed: the style is regressed, with imputation_weighting weights over the universe, on one intercept per industry and a slope on the Size exposure (imputation_regressors; Size itself on the industry alone), and the symbol gets its industry’s intercept plus the slope times its Size. A symbol keeps NaN when its industry has no fitted member or a regressor of its own is missing. Every style is then standardized once more, imputed values included;

  • the NLSIZE descriptor is the cube of the Size exposure (before imputation), orthogonalized against Size, then outlier-treated and standardized like any descriptor; Non-linear Size is it standardized again. NLBETA and Non-linear Beta are the same with Beta. This is the text’s order (Empirical Notes p.55), so the outlier step can leave a small weighted correlation with the regressor;

  • an orthogonalization is a weighted least-squares regression with an intercept, weighted by orthogonalization_weighting and fitted in the universe; every symbol gets its residual, a symbol missing a regressor being taken at that regressor’s weighted mean (our choice). Standardizing afterwards keeps the weighted correlation with the regressors at zero;

  • last, a symbol whose price_column is NaN on the bar gets NaN in every descriptor and style (not industry or estu). Only the outputs are masked, so nothing above sees the mask: a windowed descriptor, which would stay defined while its window holds enough past returns, and carried fundamentals end with the prices. This reads bar t only, with no look-ahead; a halted stock has no exposure on its halt days.

Outputs, all on (timestamp, symbol):

  • desc_lncap, desc_beta, desc_rstr, desc_dastd, desc_cmra, desc_hsigma, desc_nlsize, desc_nlbeta, desc_stom, desc_stoq, desc_stoa, desc_yild, desc_btop, desc_etop, desc_cetop, desc_mlev, desc_dtoa, desc_blev, desc_egro, desc_sgro: the outlier-treated, standardized descriptors;

  • style_size, style_beta, style_momentum, style_residual_volatility, style_nonlinear_size, style_nonlinear_beta, style_liquidity, style_dividend_yield, style_book_to_price, style_earnings_yield, style_leverage, style_growth: the style exposures;

  • industry: the industry code, unchanged, on every bar;

  • estu: 1 inside the estimation universe of the bar, 0 outside.

The graph runs in double precision. On 1000 bars of 3008 symbols, with a 252-bar BETA window of half-life 63, float32 runs 2x faster but leaves errors of 4e-7 in BETA and 6.6e-5 in standardized LNCAP against a float64 reference, where double leaves 4e-15 and 3e-10. The factor is batch only: mode="stream" is refused.

Parameters:
configFactorConfig

mode must be "batch", data_columns must name exactly BarraStyleParameters.panel_columns, factor_names may pick any subset of the outputs, and warmup_bars should be at least BarraStyleParameters.warmup_bars (526 by default). With risk_free_symbol the warm-up is counted in bars where some symbol has a price: a date only the rate has (FRED’s DTB3 has one on 2025-01-09, when NYSE was closed) is dropped before the graph runs, so it does not count.

Raises:
ValueError

If mode is not "batch", data_columns does not match the parameters, factor_names names an unknown output, or kwargs holds an unknown or invalid parameter.

Examples

prices is a SharadarStockDataset, daily a SharadarDailyDataset and risk_free a dataset holding the risk_free variable on the same symbols.

>>> factor = BarraStyle(FactorConfig(
...     warmup_bars=526,
...     dataset=[prices, daily, risk_free],
...     mode="batch",
...     data_columns=("adjClose", "marketcap", "risk_free", "close", "volume",
...                   "divCash", "splitFactor"),
...     file_path="barra_style.zarr",
... ))
>>> factor.get_factor_names()[:4]
('desc_lncap', 'desc_beta', 'desc_rstr', 'desc_dastd')
init_stream()[source]#

Refuse: BarraStyle has no streaming graph.

Raises:
ValueError

Always.

Examples

>>> factor.init_stream()
Traceback (most recent call last):
    ...
ValueError: BarraStyle is batch only and has no streaming graph