Package: portsort
portsortThe PortSort class enables single, double or triple sorting of entities in portfolios. Construction of portfolios based
on firm characteristics is possible with the ff_portfolios() method.
Sorts can be conditional or uncondtional or a mix of both in triple sorting.How to installpipinstallportsortHow to useThe PortSort_Example.py file is used to demonstrate the functionality of the PortSort class and its methods.A panel dataset of the characteristics of 800 random US domiciled and traded securities for 2018-2020 and a
dataset of monthly returns are used for the example.The 'FirmCharacteristics2018.csv' dataset has 8 columns:'year' : Calendar year'notPERMNO' : Randomized security identifier based on the true CRSP PERMNO'CAP' : Market Capitalization of the last business day of the current year'CAP_W' :  Market Capitalization of the last business day of the previous year'RET_total' : Total return (including dividends) of a stock in a year'SPREAD_PC_median' : Daily median of the ratio of the Bid-Ask spread over the closing price of the stock in a year'FF30' : Fama-French 30 industry classification of a security based on its SIC.'EXCHCD' : Market exchange in which the security is traded. 1 is NYSE, 2 is AMEX and 3 is NASDAQ.The 'STOCKmonthlydata2019.csv' dataset has 4 columns:'date_m': Date in YYYYmm format.'year' : Calendar year'RET' : Returns of securities in monthly frequency (not in percentage).'notPERMNO' : Randomized security identifier based on the true CRSP PERMNO"""Examples of the PortSort class and its methods."""importosimportpandasaspdimportnumpyasnp# Main directorywdir=r'C:\Users\ropot\OneDrive\Desktop\PortSort-main'os.chdir(wdir)# Import the PortSort class. For more details:# https://github.com/ioannisrpt/portsort.git# pip install portsortfromportsortimportportsortasps# Import FirmCharacteristics table (annual frequency)ftotype32={'year':np.int32,'CAP':np.float32,'CAP_W':np.float32,'RET_total':np.float32,'SPREAD_PC_median':np.float32,'EXCHCD':np.int32,'notPERMNO':np.int32}firmchars=pd.read_csv(os.path.join(wdir,'FirmCharacteristics2018.csv')).astype(ftotype32)# Import return data (monthly frequency)ctotype32={'RET':np.float32,'date_m':np.int32,'year':np.int32,'notPERMNO':np.int32}crspm=pd.read_csv(os.path.join(wdir,'STOCKmonthlydata2019.csv')).astype(ctotype32)# Define the PortSort classportchar=ps.PortSort(df=firmchars,entity_id='notPERMNO',time_id='year',save_dir=wdir)# -------------# single_sort()# -------------# Single sort stocks into quintile portfolios based on  the market# capitalization of the last business day of the previous year ('CAP')portchar.single_sort(firm_characteristic='CAP',lagged_periods=1,n_portfolios=5)print(portchar.single_sorted.head(20))# Single sort stocks into 3 portfolios (30%, 40% 30%) based on the market# capitalization of the last business day of the previous year ('CAP').# NYSE breakpoints for size are used.portchar.single_sort(firm_characteristic='CAP',lagged_periods=1,n_portfolios=np.array([0,0.3,0.7]),quantile_filter=['EXCHCD',1])print(portchar.single_sorted.head(20))# -------------# double_sort()# -------------# Double sort stocks unconditionally into 5x2 portfolios based on the market# capitalization of the last business day of the previous year ('CAP') and the# total annual return of the past year ('RET_total').portchar.double_sort(firm_characteristics=['CAP','RET_total'],lagged_periods=[1,1],n_portfolios=[5,2])print(portchar.double_sorted.head(20))# Double sort stocks conditionally into 3x2 portfolios based on  the market# capitalization of the last business day of the previous year ('CAP') and the# total annual return of the past year ('RET_total').# NYSE breakpoints for size are used.portchar.double_sort(firm_characteristics=['CAP','RET_total'],lagged_periods=[1,1],n_portfolios=[np.array([0,0.3,0.7]),2],quantile_filters=[['EXCHCD',1],None],conditional=True)print(portchar.double_sorted.head(20))# -------------# triple_sort()# -------------# Triple Sort stocks unconditionally into 2x2x2 portfolios based on  the market# capitalization of the last business day of the previous year ('CAP'), total# annual return ('RET_total') and daily median spread percentage# ('SPREAD_PC_median') of the past year.# NYSE breakpoitns are used for size and spread percentage# but not for total return.portchar.triple_sort(firm_characteristics=['CAP','RET_total','SPREAD_PC_median'],lagged_periods=[1,1,1],n_portfolios=[2,2,2],quantile_filters=[['EXCHCD',1],None,['EXCHCD',1]])print(portchar.triple_sorted.head(20))# Triple Sort stocks into 2x2x2 portfolios based on  the market capitalization# of the last business day of the previous year ('CAP'), total annual return# ('RET_total') and daily median spread percentage ('SPREAD_PC_median') of the# past year.# First stocks are uncondtionally sorted by size and total annual return and# then within these portfolios they are conditionally sorted by spread.# If A, B, C are the characteristics in that order and '+', '|' correspond# to intersection and conditionality of sets, then conditional = [False, True]# is equivalent to  C|(A+B).# Type help(PortSort.triple_sort) for more details.# NYSE breakpoitns are used for size and spread percentage# but not for total return.portchar.triple_sort(firm_characteristics=['CAP','RET_total','SPREAD_PC_median'],lagged_periods=[1,1,1],n_portfolios=[2,2,2],quantile_filters=[['EXCHCD',1],None,['EXCHCD',1]],conditional=[False,True])print(portchar.triple_sorted.head(20))# Triple Sort stocks into 2x2x2 portfolios based on  the market capitalization# of the last business day of the previous year ('CAP'), total annual return# ('RET_total') and daily median spread percentage ('SPREAD_PC_median') of the# past year. Entities conditional on size, are then sorted into 2x2# unconditional return and spread portfolios.# If A, B, C are the characteristics in that exact order and# '+', '|' correspond to intersection and conditionality of sets,# then conditional = [True, False] is  equivalent to (B + C)| A.# Type help(PortSort.triple_sort) for more details.# NYSE breakpoitns are used for size and spread percentage# but not for total return.portchar.triple_sort(firm_characteristics=['CAP','RET_total','SPREAD_PC_median'],lagged_periods=[1,1,1],n_portfolios=[2,2,2],quantile_filters=[['EXCHCD',1],None,['EXCHCD',1]],conditional=[True,False])print(portchar.triple_sorted.head(20))# ---------------------# augment_last_traded()# ---------------------# First we adjust for delisted returns during the calendar# year. PortSort handles the firm characteristics and the# return dataframe separately and only merge them together# at the very end for the calculation of portfolio returns.# As such, we need to augment the characteristics dataset# with the data for stocks that are delisted but need to be# included in the sorting procedure. augment_last_traded()# method allows for that adjustment while it fills the extra# rows with the weighting variable 'CAP_W' and the exchange# market 'EXCHCD'. If we don't adjust for the delistings,# our results will suffer from look-ahead bias.portchar.augment_last_traded(ret_data=crspm,ret_time_id='date_m',col_w='CAP',col_w_lagged_periods=1,col_w_suffix='W',fill_cols=['EXCHCD'])# ---------------# ff_portfolios()# ---------------# Monthly returns of 10 value-weighted portfolios on size ('CAP').# NYSE breakpoints are used.portchar.ff_portfolios(ret_data=crspm,ret_time_id='date_m',ff_characteristics=['CAP'],ff_lagged_periods=[1],ff_n_portfolios=[10],ff_quantile_filters=[['EXCHCD',1]],weight_col='CAP_W',return_col='RET',ff_save=True)print(portchar.portfolios.head(30))print(portchar.num_stocks)# Monthly returns of 3x2 value-weighted portfolios on size ('CAP') and# liquidity ('SPREAD_PC_median').# The sort is unconditional and NYSE breakpoints are used for size.# By specifying the market_cap_cols, the portfolio turnover is also returned.# market_cap_cols is a list =# [capitalization of the stock at the end of the previous period,# capitalization of the stock at the ned of the current period]portchar.ff_portfolios(ret_data=crspm,ret_time_id='date_m',ff_characteristics=['CAP','SPREAD_PC_median'],ff_lagged_periods=[1,1],ff_n_portfolios=[np.array([0,0.3,0.7]),2],ff_quantile_filters=[['EXCHCD',1],None],ff_conditional=[False],weight_col='CAP_W',return_col='RET',market_cap_cols=['CAP_W','CAP'],ff_save=True)print(portchar.portfolios.head(30))print(portchar.num_stocks)print(portchar.turnover)print('Acess the explicit portfolio weights of the stocks:\n')print(portchar.hdf.head(20))# Monthly returns of 2x2x2 value-weighted portfolios on size ('CAP'),# liquidity ('SPREAD_PC_median') and annual returns ('RET_total') of the# previous year.# The sorts are all conditional (the order matters).# NYSE breakpoints are used only for size.portchar.ff_portfolios(ret_data=crspm,ret_time_id='date_m',ff_characteristics=['CAP','SPREAD_PC_median','RET_total'],ff_lagged_periods=[1,1,1],ff_n_portfolios=[2,2,2],ff_quantile_filters=[['EXCHCD',1],None,None],ff_conditional=[True,True],weight_col='CAP_W',return_col='RET',market_cap_cols=['CAP_W','CAP'],ff_save=True)print(portchar.portfolios.head(30))print(portchar.num_stocks)print(portchar.turnover)# Monthly returns of 2x2x2 value-weighted portfolios on size ('CAP'),# liquidity ('SPREAD_PC_median') and annual returns ('RET_total') of the# previous year.# Entities conditional on size, are then sorted into 2x2 unconditional return# and spread portfolios.# If A, B, C are the characteristics in that exact order and# '+', '|' correspond to intersection and conditionality of sets,# then conditional = [True, False] is  equivalent to (B + C)| A.# Type help(PortSort.ff_portfolios) for more details.# NYSE breakpoints are used for size.portchar.ff_portfolios(ret_data=crspm,ret_time_id='date_m',ff_characteristics=['CAP','SPREAD_PC_median','RET_total'],ff_lagged_periods=[1,1,1],ff_n_portfolios=[2,2,2],ff_quantile_filters=[['EXCHCD',1],None,None],ff_conditional=[True,False],weight_col='CAP_W',return_col='RET',market_cap_cols=['CAP_W','CAP'],ff_save=True)print(portchar.portfolios.head(30))print(portchar.num_stocks)print(portchar.turnover)