generated from cvxgrp/simulator
-
Notifications
You must be signed in to change notification settings - Fork 0
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
21 changed files
with
226 additions
and
7,660 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,182 +1,7 @@ | ||
# [cvxsimulator](https://www.cvxgrp.org/simulator/) | ||
# cvxcli | ||
|
||
[![PyPI version](https://badge.fury.io/py/cvxsimulator.svg)](https://badge.fury.io/py/cvxsimulator) | ||
[![Apache 2.0 License](https://img.shields.io/badge/License-APACHEv2-brightgreen.svg)](https://github.com/cvxgrp/simulator/blob/master/LICENSE) | ||
[![PyPI download month](https://img.shields.io/pypi/dm/cvxsimulator.svg)](https://pypi.python.org/pypi/cvxsimulator/) | ||
[![Coverage Status](https://coveralls.io/repos/github/cvxgrp/simulator/badge.png?branch=main)](https://coveralls.io/github/cvxgrp/simulator?branch=main) | ||
[![PyPI version](https://badge.fury.io/py/cvxcli.svg)](https://badge.fury.io/py/cvxcli) | ||
[![Apache 2.0 License](https://img.shields.io/badge/License-APACHEv2-brightgreen.svg)](https://github.com/cvxgrp/cvxcli/blob/master/LICENSE) | ||
[![PyPI download month](https://img.shields.io/pypi/dm/cvxcli.svg)](https://pypi.python.org/pypi/cvxcli/) | ||
[![Coverage Status](https://coveralls.io/repos/github/cvxgrp/simulator/badge.png?branch=main)](https://coveralls.io/github/cvxgrp/cvxcli?branch=main) | ||
|
||
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/cvxgrp/simulator) | ||
|
||
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. | ||
|
||
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. | ||
|
||
## Creating portfolios | ||
|
||
The simulator shall be completely agnostic as to the trading policy/strategy. | ||
Our approach follows a rather common pattern: | ||
|
||
* [Create the builder object](#create-the-builder-object) | ||
* [Loop through time](#loop-through-time) | ||
* [Build the portfolio](#build-the-portfolio) | ||
|
||
We demonstrate those steps with somewhat silly policies. | ||
They are never good strategies, but are always valid ones. | ||
|
||
Of course, some users may know prices and weights in advance. | ||
In that case, the building procedure can be bypassed. | ||
We discuss this in | ||
|
||
* [Bypassing the builder](#bypassing-the-builder) | ||
|
||
### Create the builder object | ||
|
||
The user defines a builder object by loading a frame of prices | ||
and initialize the amount of cash used in our experiment: | ||
|
||
```python | ||
from pathlib import Path | ||
|
||
import pandas as pd | ||
from cvx.simulator.builder import builder | ||
|
||
prices = pd.read_csv(Path("resources") / "price.csv", | ||
index_col=0, parse_dates=True, header=0).ffill() | ||
b = builder(prices=prices, initial_cash=1e6) | ||
``` | ||
|
||
It is also possible to specify a model for trading costs. | ||
The builder helps to fill up the frame of positions. Only once done | ||
we construct the actual portfolio. | ||
|
||
### 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 t, state in b: | ||
# pick two assets at random | ||
pair = np.random.choice(b.assets, 2, replace=False) | ||
# compute the pair | ||
stocks = pd.Series(index=b.assets, data=0.0) | ||
stocks[pair] = [state.nav, -state.nav] / state.prices[pair].values | ||
# update the position | ||
b[t[-1]] = 0.1 * stocks | ||
``` | ||
|
||
Here t is the growing list of timestamps, e.g. in the first iteration | ||
t is $t1$, in the second iteration it will be $t1, t2$ etc. | ||
|
||
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. | ||
|
||
Here's a slightly more realistic loop. Given a set of $4$ assets we want to | ||
implmenent the popular $1/n$ strategy. | ||
|
||
```python | ||
for t, state in b: | ||
# each day we invest a quarter of the capital in the assets | ||
b[t[-1]] = 0.25 * state.nav / state.prices | ||
``` | ||
|
||
Note that we update the position at the last element in the t list | ||
using a series of actual stocks rather than weights or cashpositions. | ||
The builder class also exposes setters for such alternative conventions. | ||
|
||
```python | ||
for t, state in b: | ||
# each day we invest a quarter of the capital in the assets | ||
b.set_weights(t[-1], pd.Series(index=b.assets, data = 0.25)) | ||
``` | ||
|
||
### Build the portfolio | ||
|
||
Once finished it is possible to build the portfolio object | ||
|
||
```python | ||
portfolio = b.build() | ||
``` | ||
|
||
### Bypassing the builder | ||
|
||
Some may know the positions the portfolio shall 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. | ||
|
||
```python | ||
from pathlib import Path | ||
|
||
import pandas as pd | ||
from cvx.simulator.portfolio import EquityPortfolio | ||
|
||
prices = pd.read_csv(Path("resources") / "price.csv", | ||
index_col=0, parse_dates=True, header=0).ffill() | ||
stocks = pd.read_csv(Path("resources") / "stock.csv", | ||
index_col=0, parse_dates=True, header=0) | ||
portfolio = EquityPortfolio(prices=prices, stocks=stocks, initial_cash=1e6) | ||
``` | ||
|
||
## Analytics | ||
|
||
The portfolio object supports further analysis and exposes | ||
a number of properties, e.g. | ||
|
||
```python | ||
portfolio.nav | ||
portfolio.cash | ||
portfolio.equity | ||
``` | ||
|
||
We have also integrated the [quantstats](https://github.com/ranaroussi/quantstats) | ||
package for further analysis. Hence it is possible to perform | ||
|
||
```python | ||
portfolio.snapshot() | ||
portfolio.metrics() | ||
portfolio.plots() | ||
portfolio.html() | ||
``` | ||
|
||
We also added an enum | ||
|
||
```python | ||
portfolio.plot(kind=Plot.DRAWDOWN) | ||
``` | ||
|
||
supporting all plots defined in quantstats. | ||
|
||
![quantstats snapshot](portfolio.png) | ||
|
||
## Poetry | ||
|
||
We assume you share already the love for [Poetry](https://python-poetry.org). | ||
Once you have installed poetry you can perform | ||
|
||
```bash | ||
make install | ||
``` | ||
|
||
to replicate the virtual environment we have defined in pyproject.toml. | ||
|
||
## Kernel | ||
|
||
We install [JupyterLab](https://jupyter.org) within your new virtual | ||
environment. Executing | ||
|
||
```bash | ||
make kernel | ||
``` | ||
|
||
constructs a dedicated [Kernel](https://docs.jupyter.org/en/latest/projects/kernels.html) | ||
for the project. |
File renamed without changes.
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 |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import fire # type: ignore | ||
import numpy as np | ||
|
||
from cvx.bson.file import read_bson | ||
|
||
|
||
def smallest_ev(bson_file) -> None: | ||
""" | ||
Compute the smallest eigenvalue of a matrix stored in a bson file. | ||
The key for the matrix shall be "cov". | ||
There are faster methods to compute the smallest eigenvalue, e.g. inverse power iteration. | ||
Here, we only use this as an example to work with the bson interface | ||
On the command line | ||
poetry run smallest-eigenvalue cli/data/test.bson | ||
""" | ||
matrix = read_bson(bson_file)["cov"] | ||
w, _ = np.linalg.eigh(matrix) | ||
return np.min(w) | ||
|
||
|
||
def main(): | ||
fire.Fire(smallest_ev) |
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 |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import fire # type: ignore | ||
import requests # type: ignore | ||
|
||
|
||
def cli(metric: str, latitude: float = 37.4419, longitude: float = -122.143) -> None: | ||
""" | ||
Get the current weather for a given metric | ||
Parameters | ||
---------- | ||
metric : str | ||
The metric to get the current weather for | ||
latitude : float, optional | ||
The latitude to get the current weather for, by default 37.4419 | ||
longitude : float, optional | ||
The longitude to get the current weather for, by default -122.143 | ||
""" | ||
url = "https://api.open-meteo.com/v1/forecast" | ||
url = f"{url}?latitude={str(latitude)}&longitude={str(longitude)}¤t_weather=true" | ||
r = requests.get(url) | ||
|
||
if r.status_code == 200: | ||
if metric in r.json()["current_weather"]: | ||
x = r.json()["current_weather"][metric] | ||
return x | ||
else: | ||
raise ValueError("Metric not supported!") | ||
else: | ||
raise ConnectionError("Open-Meteo is down!") | ||
|
||
|
||
def main(): # pragma: no cover | ||
""" | ||
Run the CLI using Fire | ||
""" | ||
fire.Fire(cli) |
Binary file not shown.
Oops, something went wrong.