# OpenSourceAP/CrossSection: the signals and portfolios behind the open asset pricing paper

> CrossSection is the GPL-2.0 licensed repository that accompanies Chen and Zimmermann's open source cross-sectional asset pricing paper, holding roughly 15,000 lines of Python and R that build stock level characteristics from WRDS and FRED data and turn them into portfolios. It is deliberately split into three folders so that a user can run only the part they need, and the smallest path is three signals and about thirty minutes of compute.

**OpenSourceAP/CrossSection** — Code to accompany our paper Chen and Zimmermann (2020), "Open source cross-sectional asset pricing"

- Repository: https://github.com/OpenSourceAP/CrossSection
- Website: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3604626
- Stars: 1,069 · Forks: 290
- Language: Python
- License: GPL-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/opensourceap-crosssection

## A repository that exists to make a paper reproducible

This repository accompanies the paper Chen and Zimmermann, Open source cross-sectional asset pricing, published in the Critical Finance Review, volume 11, number 2, pages 207 to 264, with the year given as 2022 in the citation and 2021 in the README's title reference.

The framing is reproducibility in the strict sense. The repository contains the code that builds the signals and the portfolios, and the authors ask that if you use the data or the code, you cite the paper.

What the paper produced is a large collection of stock level characteristics, and the repository is the machinery that turns raw vendor data into them. The authors also publish the data itself, which is the important division. If you are mostly interested in working with the data, the repository points you to a dedicated data page with stock level signals, the characteristics, and a bunch of different portfolio implementations for direct download, along with a FAQ page for that data.

The repository is still useful for understanding the data even if you never run it, and the example given is a good one. If you want to know exactly how BrandInvest is constructed, following Belo, Lin and Vitorino, you can open `BrandInvest.py` in the predictors directory and read it. That is the case the repository serves best, as the reference implementation of a signal whose published definition is a paragraph long and whose actual computation is a page of code.

## Three folders, and permission to pick

The code is separated into three folders, and the stated reason is that you can choose which parts you want to run.

`Signals/pyCode/` downloads data from WRDS and elsewhere, constructs stock level signals in Python, and outputs to `Signals/pyData/`. `Portfolios/Code/` takes in signals and outputs portfolios to `Portfolios/Data/`, and it is entirely in R. `Shipping/Code/` is used to prepare the data for sharing, and the README says you should not need to use it but that it is kept with the rest of the code for replicability.

The README is candid about the size, calling the whole thing about 15,000 lines and suggesting you might want to pick your battles. That is a fair warning, and it also explains why the split exists. A user who only wants the signals runs the Python and does their own work with the output. A user who only wants portfolios skips signal generation entirely and downloads the signal output from the data page.

The Python side has its own internal structure, and it is worth knowing before you start. `master.py` runs the end to end pipeline and calls staged scripts in `DataDownloads/`, which downloads data and writes to `Signals/pyData/`, `SignalMasterTable.py`, which builds the join table used across predictors, `Predictors/`, which constructs the stock level predictors, and `Placebos/`, which constructs not predictors and indirect evidence signals. Both predictors and placebos output into their own directories under `Signals/pyData/`.

## Signals need WRDS, FRED and a five hour wait

The signal stage has real dependencies, and the setup is documented step by step.

First, the Python dependencies, from the requirements file in the signal directory, and the required R packages, which the README marks as to be confirmed. Then a credentials file: copy `dotenv.template` to `.env` and add your WRDS and FRED credentials, and for FRED you request an API key.

Then an optional preparation step, which the README says is only necessary for a handful of signals. From the signal directory you run `prep1_run_on_wrds.sh` to copy the prep scripts to the WRDS Cloud, you wait about five hours, checking with `qstat` whether the job is still running, and if you are impatient you look at the most recent file in the log directory on the WRDS server. Then `prep2_dl_from_wrds.sh` downloads the prep data from the WRDS Cloud to the prep data directory. You can alternatively upload to the WRDS Cloud manually, ssh in, run the submit script with `qsub`, and download the results yourself.

So the pipeline runs on a remote compute queue, not on your laptop, which is worth understanding before you plan around it.

The run itself is one command:

```bash
cd Signals/pyCode/
python master.py
```

And the design decision in the orchestrator is the one that makes it usable. The orchestrator blocks are written to keep running even if a particular download fails, for example due to a missing subscription, so you get as much data as possible. Progress is tracked in the logs directory.

That resilience is not a small thing in a pipeline that touches external data sources, because a single expired subscription would otherwise stop everything and lose the hours of work that had already completed.

## Placebos are a first class output, not an afterthought

The `Placebos/` directory deserves a paragraph of its own, because it is the part of the design that tells you what kind of work this is.

The placebos construct not predictors and indirect evidence signals. A not predictor is a signal that has no economic content, and running the full portfolio construction over a large set of them is how you find out how much of your result was real and how much was the search process itself. The indirect evidence signals are the other half, the proxies for the mechanism you are claiming to have found.

Producing placebos in the same pipeline as the real signals, and shipping them alongside, is what makes the published results falsifiable by someone else. A repository that only produced the signals that worked would be a repository you could not learn much from.

The same reasoning shows up in the signal definitions themselves, and the repository's own structure acknowledges that a signal is a piece of code rather than a number. Each predictor is a Python file, the data page describes what each one computes, and `SignalDoc.csv` is a machine readable index that the portfolio code reads to decide what to construct. That file is the contract between the two halves of the project, and it is worth reading before you run either side.

## Portfolios: master.R, an index file and an eight hour run

The portfolio side is R, and `master.R` runs everything. It takes in the signal data from either the data directory or the Python output directory, outputs portfolio data to the portfolios directory, and outputs the exhibits found in the paper to a results directory. It also uses `SignalDoc.csv` as a guide for how to run the portfolios.

There is one manual step before the first run. You must set `pathProject` in `master.R`, at line 30, to your project root directory, the one where `SignalDoc.csv` sits, and if you are using RStudio then `pathProject = paste0(getwd(), '/')` should work automatically.

The run is:

```bash
cd Portfolios/Code/
Rscript master.R
```

or open `master.R` in RStudio and click Source, or press Ctrl and Shift and S, or Cmd and Shift and S on a Mac.

The time cost is stated precisely and it depends on what you ask for. By default the code skips the daily portfolios, with `skipdaily = T`, and takes about eight hours assuming you examine all three hundred or so signals. The baseline portfolios, based on the predictability results in the original papers, are done in just thirty minutes. Progress is visible in the csv files output to the portfolios directory, which should get another set every thirty minutes or so. Adding the daily portfolios, with `skipdaily = F`, takes an additional twelve hours or so.

So the shape of the cost is a short run for a handful of signals and a long one for everything, and the switch between them is a single variable.

## The minimal path, and where it goes wrong

The README tells you how to get started quickly, and it is a good idea to follow it. With a minimal setup, `master.R` will create portfolios for Price, Size and STreversal.

There are a couple of ways to set up the signal data for that. You can run the code in the signal directory as described above. You can download the individual predictor and placebo zip files from the data page and unzip them into the two signal data directories. Or you can download only some selected csv files from the data page and place them in the predictors directory, with the example given being just `BM.csv`, `AssetGrowth.csv` and `EarningsSurprise.csv`.

That third option is the one that makes the project approachable, because three signals and thirty minutes of compute is a sized experiment. The first option, running the full signal construction, needs WRDS access, an FRED key, a queue submission and about five hours of waiting, and for a reader who wants to know how the signals are built that is a lot of infrastructure to stand up before the first result.

The failure modes follow from the same shape. A missing WRDS subscription produces a partially populated data set rather than an error, because the orchestrator is built to keep going, so a run that finishes with fewer signals than you expected is a normal outcome and the logs are where you look. A stale or wrong `pathProject` gives you an empty or partial portfolio set, and the file name in the `SignalDoc.csv` index is what tells you whether a signal was actually constructed.

And there is a subtler class of problem, which the release history hints at. One of the recent tags, v1.4.1, is titled AnnouncementReturn lookahead bias patch, which is a correction to a signal's construction rather than to its data. If you are comparing your output to a published table, check which version of the signal definition produced it, because a lookahead bias patch changes numbers that look otherwise identical.

## Conclusion

Adopt this repository if you are reproducing or extending published cross-sectional asset pricing results, because it gives you the exact signal definitions and the portfolio construction code rather than a summary of them. Do not adopt it as a data source on its own, since the machine readable signal and portfolio files are distributed separately through the openassetpricing.com data page, and the repository is a pipeline that needs WRDS access to run end to end. Verify first that you have WRDS credentials and a FRED API key before starting, since the signal stage is a multi hour job on a cloud queue, and read the release history, because a lookahead bias patch in the announcement return signal shows that the signal definitions have changed for correctness reasons rather than only for new data.

## FAQ

### What is the CrossSection repository for?

It accompanies the paper Chen and Zimmermann, Open source cross-sectional asset pricing, published in the Critical Finance Review, and contains the code that builds the stock level signals and the portfolios, about 15,000 lines of Python and R in total. The machine readable data itself is distributed separately through the openassetpricing.com data page.

### What are the three parts of the CrossSection codebase?

`Signals/pyCode/` downloads data from WRDS and elsewhere, constructs stock level signals in Python and outputs to `Signals/pyData/`. `Portfolios/Code/` takes signals and outputs portfolios to `Portfolios/Data/`, entirely in R. `Shipping/Code/` prepares the data for sharing and is kept for replicability rather than for daily use.

### What do I need to run the CrossSection signal code?

The Python dependencies from `requirements.txt`, the required R packages, and credentials. You copy `dotenv.template` to `.env` and add your WRDS and FRED credentials, with the FRED side needing an API key you request. A handful of signals also need a prep step run on the WRDS Cloud, which takes about five hours, before `python master.py` runs the pipeline.

### How long does the CrossSection portfolio code take?

With the default `skipdaily = T`, running `Rscript master.R` over all three hundred or so signals takes about eight hours, while the baseline portfolios based on the original papers' predictability results take about thirty minutes. Adding the daily portfolios with `skipdaily = F` adds roughly twelve hours, and progress appears as csv files in the portfolios output directory every thirty minutes or so.

### What are the placebos in CrossSection for?

The `Placebos/` directory constructs not predictors and indirect evidence signals, the proxies for a claimed mechanism. Producing them in the same pipeline as the real signals, and shipping them alongside, is what lets someone else check how much of a published result was signal and how much was the search process.

## Sources

- [License: GPL-2.0](https://github.com/OpenSourceAP/CrossSection/blob/master/LICENSE)
- [OpenSourceAP/CrossSection on GitHub](https://github.com/OpenSourceAP/CrossSection)
- [Project website](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3604626)
- [README](https://github.com/OpenSourceAP/CrossSection/blob/master/README.md)
- [Releases](https://github.com/OpenSourceAP/CrossSection/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/opensourceap-crosssection
