Skip to main content

portsort

The 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 install

pip install portsort

How to use

The 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:

  1. 'year' : Calendar year
  2. 'notPERMNO' : Randomized security identifier based on the true CRSP PERMNO
  3. 'CAP' : Market Capitalization of the last business day of the current year
  4. 'CAP_W' : Market Capitalization of the last business day of the previous year
  5. 'RET_total' : Total return (including dividends) of a stock in a year
  6. 'SPREAD_PC_median' : Daily median of the ratio of the Bid-Ask spread over the closing price of the stock in a year
  7. 'FF30' : Fama-French 30 industry classification of a security based on its SIC.
  8. '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:

  1. 'date_m': Date in YYYYmm format.
  2. 'year' : Calendar year
  3. 'RET' : Returns of securities in monthly frequency (not in percentage).
  4. 'notPERMNO' : Randomized security identifier based on the true CRSP PERMNO
"""
Examples of the PortSort class and its methods.
"""

import os
import pandas as pd
import numpy as np



# Main directory
wdir = 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 portsort 
from portsort import portsort as ps


# 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 class
portchar = 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)

Metadata

Release files for portsort 0.2.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for portsort 0.2.5
File Size Uploaded
portsort-0.2.5.tar.gz 32.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for portsort 0.2.5
File Interpreter ABI Platform
portsort-0.2.5-py3-none-any.whl Python 3 none any Details

Total release size: 62.3 kB

Release files / portsort-0.2.5.tar.gz

Download URL portsort-0.2.5.tar.gz
Size 32.4 kB
Tags Source
SHA-256 checksum
How to use checksums
393e9848445d8fe0189d841c0b685da956a720bb188d62a110f2ee34bfec2129
BLAKE2b-256 checksum
How to use checksums
0b861c5693322c383fb6ab3234b459e83b94369a7ccbc295c69c121d3197bd29
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.1 CPython/3.10.4

Release files / portsort-0.2.5-py3-none-any.whl

Download URL portsort-0.2.5-py3-none-any.whl
Size 29.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e7ff964f528e47bb50bcd0cebd88b9cac1bc9bb9ca3f7ee01227e6b5ca439cd1
BLAKE2b-256 checksum
How to use checksums
6a6e00aedd975f49980b5c05b8e8c63c0f3041b38d7f2104d4621bdb511e09b0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.1 CPython/3.10.4

Release history Release notifications | RSS feed

This release

0.2.5 This release

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page