Segmented Arbitrage: Corporate CDS-Bond Basis (Project 12)#

Last updated: Mar 15, 2026, 7:36:02 PM

Table of Contents#

Pipeline Charts 📈

Pipeline Specs#

Pipeline Name

Segmented Arbitrage: Corporate CDS-Bond Basis (Project 12)

Pipeline ID

SE

Lead Pipeline Developer

Michelacci and Ferreira

Contributors

Michelacci and Ferreira

Git Repo URL

JacopoMichelacci/p12_siriwardane_sunderam_wallen_2023

Pipeline Web Page

Pipeline Web Page

Date of Last Code Update

2026-03-15 14:17:12

OS Compatibility

Linked Dataframes

SE:cds_basis_aggregated
SE:cds_basis_non_aggregated

About this project#

Replication of the CDS-bond basis component of “Segmented Arbitrage” by Emil Siriwardane, Adi Sunderam, and Jonathan Wallen (2023). The paper examines various arbitrage trades and observes that the price dynamics of these trades challenge asset pricing models that have been used in the past to explain deviations from no-arbitrage.

The CDS-bond basis represents the difference between the credit spread implied by a corporate bond and the premium on a credit default swap (CDS) referencing the same issuer. In theory, these two measures of credit risk should be equal; deviations create arbitrage opportunities where traders can profit by buying the cheaper instrument and selling the more expensive one.

This project builds a WRDS-based pipeline to:

  • merge bonds to RED entities,

  • interpolate CDS par spreads at bond maturity,

  • estimate bond-level z-spreads,

  • compute CDS-bond basis spread (par_spread - z_spread),

  • generate final datasets, charts, notebook tour, docs, and a LaTeX report.

Quick Start#

The quickest way to run code in this repo is to use the following steps.

You must have TexLive (or another LaTeX distribution) installed on your computer and available in your path. You can do this by downloading and installing it from here (windows and mac installers).

First, you must have the conda package manager installed (e.g., via Anaconda). However, I recommend using mamba, via miniforge as it is faster and more lightweight than conda.

Create and activate the conda environment:

conda env create -f environment.yml
conda activate segmented_arbitrage_cds_basis

Set WRDS credentials in .env (starting from .env.example):

WRDS_USERNAME=your_wrds_username

Run the full project tasks:

doit

Other commands#

Core pipeline tasks#

# Pull source datasets
doit pull_wrds_bond_ret
doit pull_markit_mapping
doit pull_wrds_markit
doit pull_CRSP_treasury
doit pull_yield_curve_data

# Run merge + z-spread + final basis outputs
doit calc

# Build chart, notebook tour, docs, and report
doit generate_charts
doit run_cds_notebooks
doit generate_pipeline_site
doit export_report_tables
doit generate_report

Unit Tests and Doc Tests#

You can run the unit test, including doctests, with the following command:

pytest --doctest-modules

Fast test run:

pytest -q

Setting Environment Variables#

You can export your environment variables from your .env files like so, if you wish. This can be done easily in a Linux or Mac terminal with the following command:

set -a  # automatically export all variables
source .env
set +a

On Windows (PowerShell):

Get-Content .env | ForEach-Object { if ($_ -match '^([^=]+)=(.*)$') { [Environment]::SetEnvironmentVariable($matches[1], $matches[2], 'Process') } }

Formatting#

This project uses Ruff for linting and formatting Python code.

# Auto-fix linting issues (e.g., unused imports, undefined names)
ruff check . --fix

# Format code (consistent style, spacing, line length)
ruff format .

# Sort imports, then fix linting issues, then format
ruff format . && ruff check --select I --fix . && ruff check --fix .
  • ruff check --fix applies safe auto-fixes for linting violations

  • ruff format formats code similar to Black

  • --select I targets only import sorting rules (isort-compatible)

Project-specific pipeline (what we implemented)#

Replication definition#

For each bond-date observation:

cds_basis_spread = par_spread - z_spread

with:

  • par_spread: CDS par spread interpolated from tenors (1Y,3Y,5Y,7Y,10Y) to bond maturity.

  • z_spread: solved from bond pricing using NSS Treasury discount factors.

Final reporting uses two analysis windows:

  • replication window: 2010-01 to 2020-02

  • full period window: 2010-01 to 2024-12

Data sources#

WRDS:

  • wrdsapps_bondret.bondret

  • markit_red.redobllookup

  • markit_cds.cds2010markit_cds.cds2024

  • CRSP Treasury datasets (pulled via pull_CRSP_treasury.py)

Federal Reserve:

  • feds200628.csv (GSW/NSS table) used for fallback checks on problematic curve-fit dates.

Main scripts#

  • src/pull_wrds_bond_ret.py

  • src/pull_markit_mapping.py

  • src/pull_wrds_markit.py

  • src/pull_CRSP_treasury.py

  • src/pull_yield_curve_data.py

  • src/merge_cds_bond.py

  • src/merge_z_spread_bond.py

  • src/process_z_spread.py

  • src/process_final_product.py

  • src/process_pipeline.py

Main outputs#

Data outputs in _data/:

  • final_data.parquet

  • final_data_with_z_spread.parquet

  • cds_basis_processed.parquet

  • cds_basis_aggregated.parquet

  • cds_basis_non_aggregated.parquet

  • cds_basis_summary_stats.csv

Report/chart outputs in _output/:

  • cds_basis_by_rating.png

  • cds_bond_basis_replication.html

  • summary_cds_bond_basis_ipynb.html

  • cds_basis_summary_stats.tex

  • cds_basis_authors_basis_comparison.png

  • cds_basis_authors_bond_count_comparison.png

  • cds_basis_authors_comparison_stats.tex

  • cds_basis_report.pdf

Note on NSS robustness#

process_z_spread.get_nss_params_for_date first fits CRSP-based NSS parameters. If 1Y/5Y/10Y fitted spots are too far from Fed SVENY values on the resolved date, the function uses Fed NSS parameters for that date as a fallback.

General Directory Structure#

  • The assets folder is used for things like hand-drawn figures or other pictures that were not generated from code. These things cannot be easily recreated if they are deleted.

  • The _output folder, on the other hand, contains dataframes and figures that are generated from code. The entire folder should be able to be deleted, because the code can be run again, which would again generate all of the contents.

  • The data_manual is for data that cannot be easily recreated. This data should be version controlled. Anything in the _data folder or in the _output folder should be able to be recreated by running the code and can safely be deleted.

  • This project uses doit as a task runner (dodo.py). Re-run the full pipeline with doit from the repo root.

  • The .env file is used as a container for absolute paths and credentials that are private to each collaborator. It should not be tracked in Git.

Data and Output Storage#

I’ll often use a separate folder for storing data. Any data in the data folder can be deleted and recreated by rerunning the PyDoit command (the pulls are in the dodo.py file). Any data that cannot be automatically recreated should be stored in the “data_manual” folder. Because of the risk of manually-created data getting changed or lost, I prefer to keep it under version control if I can. Thus, data in the “_data” folder is excluded from Git (see the .gitignore file), while the “data_manual” folder is tracked by Git.

Output is stored in the “_output” directory. This includes dataframes, charts, and rendered notebooks. When the output is small enough, I’ll keep this under version control. I like this because I can keep track of how dataframes change as my analysis progresses, for example.

Of course, the _data directory and _output directory can be kept elsewhere on the machine. To make this easy, I always include the ability to customize these locations by defining the path to these directories in environment variables, which I intend to be defined in the .env file, though they can also simply be defined on the command line or elsewhere. The settings.py is responsible for loading these environment variables and doing some preprocessing on them. The settings.py file is the entry point for all other scripts to these definitions. That is, all code that references these variables and others are loaded by importing config.

Naming Conventions#

  • pull_ vs load_: Files or functions that pull data from an external data source are prepended with “pull_”. Functions that load data that has been cached in the “data” folder are prepended with “load”.

Dependencies and Virtual Environments#

Working with conda environments#

This project uses conda for environment management. The dependencies are stored in environment.yml.

To create/update the environment:

conda env create -f environment.yml
# or to update an existing environment:
conda env update -f environment.yml

To activate the environment:

conda activate segmented_arbitrage_cds_basis

To export the current environment:

conda env export > environment.yml

Tip: Consider using mamba instead of conda for faster package resolution. Install via miniforge.