@@ -71,6 +71,10 @@ A per-symbol time-series computation. Phase 1 ships these built-ins:
7171| ` SMA(window) ` | close | simple moving average |
7272| ` RSI(window) ` | close | Wilder's RSI |
7373| ` Volatility(window, periods_per_year=252) ` | close | annualised stdev of log returns |
74+ | ` StaticPerSymbol(mapping, default=None) ` | — | broadcasts a ` dict[symbol, value] ` (e.g. sector / market-cap) into the cross-section |
75+ | ` CrossSectionalMean(base, mask=None) ` | base factor | per-bar equal-weight mean across surviving symbols |
76+ | ` RollingBeta(target, market, window>=2) ` | two factors | rolling-window OLS beta ` cov(t,m)/var(m) ` ; null when ` var(m) == 0 ` |
77+ | ` Neutralize(target, exposures=[...], mask=None, add_intercept=True) ` | factors | per-bar OLS residualisation of ` target ` against ` exposures ` ; null on rank-deficient bars |
7478
7579You can also subclass ` CustomFactor ` to compute your own.
7680
@@ -101,6 +105,12 @@ factor.zscore(mask=universe) # (x - mean) / std per bar
101105factor.demean(mask = universe) # x - mean per bar
102106factor.winsorize(0.01 , 0.99 , # clip to per-bar quantiles
103107 mask = universe)
108+
109+ # Group-relative variants — stats computed within each group
110+ # (typically sector). `groups` accepts a dict[symbol, key] or any
111+ # Factor that emits a per-symbol category.
112+ factor.zscore(groups = SECTORS ) # z-score within sector
113+ factor.demean(groups = SECTORS , mask = universe)
104114```
105115
106116Where the cross-sectional ` std ` is ` 0 ` or undefined (e.g. only one
0 commit comments