Let's use Alphalens to explore the forward performance of stocks based on their Altman Z-Score. In previous notebooks we formed equal-sizes quantiles, but in this notebook we'll form bins corresponding to the Altman Z-Scores three "zones":
Defining your own bin edges can be done either in the Alphalens' from_pipeline()
function or in the Pipeline definition itself. First, we'll show how to do it in Alphalens, and then we'll show how to do it in Pipeline.
To define the bin edges in Alphalens, we can start by creating a simple Pipeline that contains the Altman Z-Score factor:
from zipline.pipeline import sharadar, Pipeline
from codeload.fundamental_factors.universe import CommonStocks, BaseUniverse
universe = BaseUniverse()
altman = sharadar.AltmanZScore('ART', mask=universe)
pipeline = Pipeline(
columns={
'altman': altman,
},
initial_universe=CommonStocks(),
screen=universe & altman.notnull()
)
The Alphalens from_pipeline()
function contains two, mutually exclusive parameters that control how the factor will be binned: quantiles
and bins
.
In the previous notebooks, we specified quantiles=n
to create n
equal-sized bins. Under the hood, this caused Alphalens to pass the factor data to pandas' qcut()
function, which calculates the bin edges needed to have the same number of items in each bin.
In this notebook, we will use the bins
argument to tell Alphalens where the bin edges should be. Under the hood, this will cause Alphalens to pass the factor data to pandas' cut()
function. To split the data into three bins, where the first bin contains everything below 0, the second bin goes from 0 to 3, and the third bin contains everything above 3, we can pass the following bins
argument:
bins=[-float("inf"), 0, 3, float("inf")]
Since we want 3 bins, we have to pass 4 bin edges; the length of the list you pass to bins
will always be the desired number of bins plus one. The range of the first bin is defined by the first two numbers (here, minus infinity and 0), the range of the second bin is defined by the second and third numbers (0 and 3), and the range of the third bin is defined by the third and fourth numbers (3 and infinity).
import alphalens as al
al.from_pipeline(
pipeline,
start_date="1998-02-01",
end_date="2022-12-30",
periods=[1, 5, 21],
factor="altman",
bins=[-float("inf"), 0, 3, float("inf")],
segment="Y"
)
min | max | mean | std | count | avg daily count | count % | |
---|---|---|---|---|---|---|---|
Altman Quantile | |||||||
1 | -326,034.031 | -0.000 | -14.856 | 1,408.404 | 1,754,284 | 279.7 | 9.4% |
2 | 0.000 | 3.000 | 1.735 | 0.777 | 6,944,924 | 1107.5 | 37.2% |
3 | 3.000 | 264,810.750 | 14.864 | 673.918 | 9,951,467 | 1586.9 | 53.4% |
1D | 21D | 5D | |
---|---|---|---|
Ann. alpha | -0.045 | -0.030 | -0.027 |
beta | 0.055 | -0.023 | 0.019 |
Mean Relative Return Top Quantile (bps) | 0.107 | -0.034 | 0.056 |
Mean Relative Return Bottom Quantile (bps) | -1.354 | -1.365 | -1.476 |
Mean Spread (bps) | 1.461 | 1.937 | 1.838 |
1D | 21D | 5D | |
---|---|---|---|
IC Mean | 0.011 | 0.018 | 0.015 |
IC Std. | 0.065 | 0.082 | 0.073 |
Risk-Adjusted IC | 0.165 | 0.216 | 0.213 |
t-stat(IC) | 13.036 | 17.082 | 16.832 |
p-value(IC) | 0.000 | 0.000 | 0.000 |
IC Skew | -0.118 | -0.264 | -0.271 |
IC Kurtosis | 0.976 | 0.272 | 0.949 |
1D | 5D | 21D | |
---|---|---|---|
Quantile 1 Mean Turnover | 0.008 | 0.037 | 0.133 |
Quantile 2 Mean Turnover | 0.004 | 0.019 | 0.070 |
Quantile 3 Mean Turnover | 0.003 | 0.013 | 0.049 |
1D | 21D | 5D | |
---|---|---|---|
Mean Factor Rank Autocorrelation | 0.999 | 0.978 | 0.995 |
1D | 21D | 5D | factor | factor_quantile | ||
---|---|---|---|---|---|---|
date | asset | |||||
1998-02-02 | Equity(FIBBG000H83MP4 [AAI1]) | 0.000000 | 0.000000 | 0.000000 | -1.967196 | 1 |
Equity(FIBBG000B9XRY4 [AAPL]) | -0.033861 | 0.262698 | 0.010377 | 1.908786 | 2 | |
Equity(FIBBG000B9Y973 [AATI1]) | 0.016695 | 0.123716 | 0.076894 | 5.829192 | 3 | |
Equity(FIBBG000MDCQC2 [COR]) | 0.031084 | 0.002296 | 0.064112 | 5.316102 | 3 | |
Equity(FIBBG000B9YDD7 [ABF]) | 0.012302 | 0.068541 | 0.054482 | 3.759253 | 3 | |
... | ... | ... | ... | ... | ... | ... |
2022-12-30 | Equity(FIBBG019QV0CG8 [SNAL]) | -0.032895 | 0.052632 | -0.006579 | 2.226664 | 2 |
Equity(FIBBG011RWR2Q4 [ACRV]) | -0.040000 | 0.350000 | -0.033333 | 12.978663 | 3 | |
Equity(FIBBG000BCVMH9 [CP]) | -0.010874 | 0.046280 | 0.028113 | 1.660714 | 2 | |
Equity(FIBBG000K5M1S8 [ENB]) | -0.004329 | 0.043290 | 0.031831 | 0.774874 | 2 | |
Equity(FIBBG000C32XT3 [IMO]) | 0.004534 | 0.128607 | -0.004328 | 3.731341 | 3 |
18650675 rows × 5 columns
min
/max
: For each factor quantile, we can check the minimum and maximum values to make sure they are as we expected. Here, everything in quantile 1 is below 0, everything in quantile 2 is between 0 and 3, and everything in quantile 3 is above 3.count %
: Because we defined the bin edges instead of using equal-sized quantiles, the quantiles are different sizes. Less than 10% of stocks are in the distress zone, ~40% are in the grey zone, and ~50% are in the safe zone.Mean Relative Return By Factor Quantile
: companies in distress perform worse than companies not in distress. There is little difference between the safe zone and the grey zone.Suppose you want to define the bin edges in Pipeline instead of in Alphalens, perhaps because you're not feeding the Pipeline output to Alphalens. A good way to do this is using the Constant
factor. Here, we define a factor for the 3 Altman Z-Score zones, with 1 being the distress zone, 2 being the grey zone, and 3 being the safe zone:
from zipline.pipeline import Constant
altman = sharadar.AltmanZScore('ART')
altman_zone = Constant(1).where(altman<=0, Constant(2).where(altman<=3, Constant(3).where(altman > 3)))