Skip to main content

A package to sort stocks into portfolios and calculate weighted-average returns.

Project description

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))


# ---------------
# 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('The raw dataframe used to calculate turnover: \n')
print(portchar.turnover_raw.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)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

portsort-0.2.3.tar.gz (31.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

portsort-0.2.3-py3-none-any.whl (29.0 kB view details)

Uploaded Python 3

File details

Details for the file portsort-0.2.3.tar.gz.

File metadata

  • Download URL: portsort-0.2.3.tar.gz
  • Upload date:
  • Size: 31.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.1 CPython/3.10.4

File hashes

Hashes for portsort-0.2.3.tar.gz
Algorithm Hash digest
SHA256 5d995964909e556f9e84cb9e1455bb88878ad013f8fc6b60943c6d1a5fc75dd3
MD5 8c77a3ecca393daddd830220babb6533
BLAKE2b-256 b5cca62cd32980ca1c24d124f566b9d52f29cc961574ffa94f63d94b18463dde

See more details on using hashes here.

File details

Details for the file portsort-0.2.3-py3-none-any.whl.

File metadata

  • Download URL: portsort-0.2.3-py3-none-any.whl
  • Upload date:
  • Size: 29.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.1 CPython/3.10.4

File hashes

Hashes for portsort-0.2.3-py3-none-any.whl
Algorithm Hash digest
SHA256 078234c89ead98521be82cd6fbc045542943d7c246baab7f851be46ec33dbd24
MD5 b70752e321ea15078268cb502df76ce1
BLAKE2b-256 d3ec94983622aef50f7573915ef23efa65895f949f7f898a584cb79966086002

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page