-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
68 additions
and
80 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,102 +1,90 @@ | ||
# Simulator | ||
# cvxcovariance | ||
|
||
Given a universe of $m$ assets we are given prices for each of them at time $t_1, t_2, \ldots t_n$, | ||
e.g. we operate using an $n \times m$ matrix where each column corresponds to a particular asset. | ||
The `cvxcovariance` package | ||
provides simple tools for creating an estimate $\hat\Sigma_t$ of the covariance $\Sigma_t$ of the $n$-dimensional return vectors $r_t$, $t=1,2,\ldots$. (Here $r_t$ is the return from $t-1$ to $t$.) The covariance predictor $\hat\Sigma_t$ is generated by blending $K$ different "expert" predictors $\hat\Sigma_t^{(1)},\ldots,\hat\Sigma_t^{(K)}$, by solving a convex optimization problem at each time step. | ||
|
||
In a backtest we iterate in time (e.g. row by row) through the matrix and allocate positions to all or some of the assets. | ||
This tool shall help to simplify the accounting. It keeps track of the available cash, the profits achieved, etc. | ||
For a detailed description of the methodology, see the our manuscript [A Simple Method for Predicting | ||
Covariance Matrices of Financial Returns](https://web.stanford.edu/~boyd/papers/cov_pred_finance.html) (in particular Section 3). | ||
|
||
## Modus operandi | ||
In the simplest case the user provides a $T\times n$ pandas DataFrame | ||
of returns $r_1,\ldots,r_T$ and $K$ half-life pairs, and gets back covariance predictors for each time | ||
step. (The $K$ experts are computed as iterated exponentially weighted moving average (IEWMA) predictors as described in Section 2.6 of the [paper](https://web.stanford.edu/~boyd/papers/cov_pred_finance.html).) In the more general case, the user provides the $K$ expert predictors $\hat\Sigma_t^{(1)},\ldots,\hat\Sigma_t^{(K)}$, $t=1,\ldots,T$, and these are blended together by solving the convex optimization problems. In either case the result is returned as an iterator object over namedtuples: `Result = namedtuple("Result", ["time", "mean", "covariance", "weights"])`. | ||
|
||
The simulator shall be completely agnostic as to the trading policy/strategy. | ||
Our approach follows a rather common pattern: | ||
|
||
* [Create the portfolio object](#create-the-portfolio-object) | ||
* [Loop through time](#loop-through-time) | ||
* [Analyse results](#analyse-results) | ||
Note: at time $t$ the user is provided with $\Sigma_{t+1}$, | ||
$\textit{i.e.}$, the covariance matrix for the next time step. So `Result.covariance` returns the covariance prediction for `time+1`. | ||
|
||
We demonstrate those steps with somewhat silly policies. They are never good strategies, but are always valid ones. | ||
## Installation | ||
To install the package, run the following command in the terminal: | ||
|
||
### Create the portfolio object | ||
|
||
The user defines a portfolio object by loading a frame of prices and initialize the initial amount of cash used in our experiment: | ||
|
||
```python | ||
import pandas as pd | ||
from cvx.simulator.portfolio import build_portfolio | ||
|
||
prices = pd.read_csv(Path("resources") / "price.csv", index_col=0, parse_dates=True, header=0).ffill( | ||
portfolio = build_portfolio(prices=prices, initial_cash=1e6) | ||
``` | ||
|
||
It is also possible to specify a model for trading costs. | ||
|
||
### Loop through time | ||
|
||
We have overloaded the `__iter__` and `__setitem__` methods to create a custom loop. | ||
Let's start with a first strategy. Each day we choose two names from the universe at random. | ||
Buy one (say 0.1 of your portfolio wealth) and short one the same amount. | ||
|
||
```python | ||
for before, now, state in portfolio: | ||
# pick two assets at random | ||
pair = np.random.choice(portfolio.assets, 2, replace=False) | ||
# compute the pair | ||
stocks = pd.Series(index=portfolio.assets, data=0.0) | ||
stocks[pair] = [state.nav, -state.nav] / state.prices[pair].values | ||
# update the position | ||
portfolio[now] = 0.1 * stocks | ||
```bash | ||
pip install cvxcovariance | ||
``` | ||
|
||
A lot of magic is hidden in the state variable. | ||
The state gives access to the currently available cash, the current prices and the current valuation of all holdings. | ||
## Usage | ||
There are two alternative ways to use the package. The first is to use the | ||
`from_ewmas` function to create a combined multiple IEWMA (CM-IEWMA) predictor. The second is to provide your own covariance experts, via dictionaries, and pass them to the `from_sigmas` function. Both functions return an object of the `_CovarianceCombination` class, which can be used to solve the covariance combination problem. | ||
|
||
Here's a slightly more realistic loop. Given a set of $4$ assets we want to implmenent the popular $1/n$ strategy. | ||
### CM-IEWMA | ||
The `from_ewmas` function takes as input a pandas DataFrame of | ||
returns and the IEWMA half-life pairs (each pair consists of one half-life for volatility estimation and one for correlation estimation), and returns an iterator object that | ||
iterates over the CM-IEWMA covariance predictors defined via namedtuples. Through the namedtuple you can access the `time`, `mean`, `covariance`, and `weights` attributes. `time` is the timestamp. `mean` is the estimated mean of the return at the $\textit{next}$ timestamp, $\textit{i.e.}$ `time+1`, if the user wants to estimate the mean; per default the mean is set to zero, which is a reasonable assumption for many financial returns. `covariance` is the estimated covariance matrix for the $\textit{next}$ timestamp, $\textit{i.e.}$ `time+1`. `weights` are the $K$ weights attributed to the experts. Here is an example: | ||
|
||
```python | ||
for _, now, state in portfolio: | ||
# each day we invest a quarter of the capital in the assets | ||
portfolio[now] = 0.25 * state.nav / state.prices | ||
import pandas as pd | ||
from cvx.covariance.combination import from_ewmas | ||
|
||
# Load return data | ||
returns = pd.read_csv("data/ff5.csv", index_col=0, header=0, parse_dates=True).iloc[:1000] | ||
n = returns.shape[1] | ||
|
||
# Define half-life pairs for K=3 experts, (halflife_vola, halflife_cov) | ||
halflife_pairs = [(10, 21), (21, 63), (63, 125)] | ||
|
||
# Define the covariance combinator | ||
combinator = from_ewmas(returns, | ||
halflife_pairs, | ||
min_periods_vola=n, # min periods for volatility estimation | ||
min_periods_cov=3 * n) # min periods for correlation estimation (must be at least n) | ||
|
||
# Solve combination problem and loop through combination results to get predictors | ||
covariance_predictors = {} | ||
for predictor in combinator.solve(window=10): # lookback window in convex optimization problem | ||
# From predictor we can access predictor.time, predictor.mean (=0 here), predictor.covariance, and predictor.weights | ||
covariance_predictors[predictor.time] = predictor.covariance | ||
``` | ||
Here `covariance_predictors[t]` is the covariance prediction for time $t+1$, $\textit{i.e.}$, it is uses knowledge of $r_1,\ldots,r_t$. | ||
|
||
Note that we update the position at time `now` using a series of actual stocks rather than weights or cashpositions. | ||
Future versions of this package may support such conventions, too. | ||
|
||
### Analyse results | ||
|
||
The loop above is filling up the desired positions. The portfolio object is now ready for further analysis. | ||
It is possible dive into the data, e.g. | ||
### General covariance combination | ||
The `from_sigmas` function takes as input a pandas DataFrame of | ||
returns and a dictionary of covariance predictors `{key: {time: | ||
sigma}`, where `key` is the key of an expert predictor and `{time: | ||
sigma}` is the expert predictions. For example, here we combine two EWMA covariance predictors from pandas: | ||
|
||
```python | ||
portfolio.nav | ||
portfolio.cash | ||
portfolio.equity | ||
... | ||
``` | ||
|
||
## The dirty path | ||
|
||
Some may know the positions they want to enter for eternity. | ||
Running through a loop is rather non-pythonic waste of time in such a case. | ||
It is possible to completely bypass this step by submitting | ||
a frame of positions together with a frame of prices when creating the portfolio object. | ||
|
||
## Poetry | ||
|
||
We assume you share already the love for [Poetry](https://python-poetry.org). Once you have installed poetry you can perform | ||
import pandas as pd | ||
from cvx.covariance.combination import from_sigmas | ||
|
||
```bash | ||
poetry install | ||
``` | ||
# Load return data | ||
returns = pd.read_csv("data/ff5.csv", index_col=0, header=0, parse_dates=True).iloc[:1000] | ||
n = returns.shape[1] | ||
|
||
to replicate the virtual environment we have defined in pyproject.toml. | ||
# Define 21 and 63 day EWMAs as dictionaries (K=2 experts) | ||
ewma21 = returns.ewm(halflife=21, min_periods=5 * n).cov().dropna() | ||
expert1 = {time: ewma21.loc[time] for time in ewma21.index.get_level_values(0).unique()} | ||
ewma63 = returns.ewm(halflife=63, min_periods=5 * n).cov().dropna() | ||
expert2 = {time: ewma63.loc[time] for time in ewma63.index.get_level_values(0).unique()} | ||
|
||
## Kernel | ||
# Create expert dictionary | ||
experts = {1: expert1, 2: expert2} | ||
|
||
We install [JupyterLab](https://jupyter.org) within your new virtual environment. Executing | ||
# Define the covariance combinator | ||
combinator = from_sigmas(sigmas=experts, returns=returns) | ||
|
||
```bash | ||
./create_kernel.sh | ||
# Solve combination problem and loop through combination results to get predictors | ||
covariance_predictors = {} | ||
for predictor in combinator.solve(window=10): | ||
# From predictor we can access predictor.time, predictor.mean (=0 here), predictor.covariance, and predictor.weights | ||
covariance_predictors[predictor.time] = predictor.covariance | ||
``` | ||
|
||
constructs a dedicated [Kernel](https://docs.jupyter.org/en/latest/projects/kernels.html) for the project. | ||
Here `covariance_predictors[t]` is the covariance prediction for time $t+1$, $\textit{i.e.}$, it is uses knowledge of $r_1,\ldots,r_t$. |