π cvxsimulator
A simple yet powerful simulator for investment strategies and portfolio backtesting.
Given a universe of
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 helps to simplify the accounting. It keeps track of the available cash, the profits achieved, etc.
Install cvxsimulator via pip:
pip install cvxsimulatorThe simulator is completely agnostic to the trading policy/strategy. Our approach follows a rather common pattern:
We demonstrate these steps with simple example policies. They are never good strategies, but are always valid ones.
The user defines a builder object by loading prices and initializing the amount of cash used in an experiment:
import pandas as pd
from cvx.simulator import Builder
# For doctest, we'll create a small DataFrame instead of reading from a file
dates = pd.date_range('2020-01-01', periods=5)
prices = pd.DataFrame({
'A': [100, 102, 104, 103, 105],
'B': [50, 51, 52, 51, 53],
'C': [200, 202, 198, 205, 210],
'D': [75, 76, 77, 78, 79]
}, index = dates)
b = Builder(prices=prices, initial_aum=1e6)Prices have to be valid, there may be NaNs only at the beginning and the end of each column in the frame. There can be no NaNs hiding in the middle of any time series.
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.
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.
import numpy as np
np.random.seed(42) # Set seed for reproducibility
for t, state in b:
# pick two assets deterministically for doctest
pair = ['A', 'B'] # Use first two assets instead of random choice
# compute the pair
units = pd.Series(index=state.assets, data=0.0)
units[pair] = [state.nav, -state.nav] / state.prices[pair].values
# update the position
b.position = 0.1 * units
# Do not apply trading costs
b.aum = state.aum
# Check the final positions
b.units.iloc[-1][['A', 'B']]Here t is the growing list of timestamps, e.g. in the first iteration
t is
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
b2 = Builder(prices=prices, initial_aum=1e6)
for t, state in b2:
# each day we invest a quarter of the capital in the assets
b2.position = 0.25 * state.nav / state.prices
b2.aum = state.aum
# Check the final positions
b2.units.iloc[-1]Note that we update the position at the last element in the t list using a series of actual units rather than weights or cashpositions. The builder class also exposes setters for such alternative conventions.
b3 = Builder(prices=prices, initial_aum=1e6)
for t, state in b3:
# each day we invest a quarter of the capital in the assets
b3.weights = np.ones(4) * 0.25
b3.aum = state.aum
# Check the final positions
b3.units.iloc[-1]Once finished it is possible to build the portfolio object:
b3 = Builder(prices=prices, initial_aum=1e6)
for t, state in b3:
b3.weights = np.ones(4) * 0.25
b3.aum = state.aum
# Build the portfolio from one of our builders
portfolio = b3.build()
# Verify the portfolio was created successfully
type(portfolio).__name__The portfolio object supports further analysis and exposes a number of properties, e.g.:
b3 = Builder(prices=prices, initial_aum=1e6)
for t, state in b3:
b3.weights = np.ones(4) * 0.25
b3.aum = state.aum
portfolio = b3.build()It is possible to generate a snapshot of the portfolio:
b3 = Builder(prices=prices, initial_aum=1e6)
for t, state in b3:
b3.weights = np.ones(4) * 0.25
b3.aum = state.aum
portfolio = b3.build()
# Generate a snapshot (returns a plotly figure)
fig = portfolio.snapshot()Start with:
make installThis will install uv and create the virtual environment defined in pyproject.toml and locked in uv.lock.
We install marimo on the fly within the aforementioned virtual environment. Execute:
make marimoThis will install and start marimo for interactive notebook development.
- Full documentation is available at cvxgrp.org/simulator/book
- API reference can be found in the documentation
- Example notebooks are included in the repository under the
bookdirectory
Contributions are welcome! Here's how you can contribute:
- Fork the repository
- Create a feature branch:
git checkout -b feature-name - Commit your changes:
git commit -m 'Add some feature' - Push to the branch:
git push origin feature-name - Open a pull request
Please make sure to update tests as appropriate and follow the code style of the project.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Copyright 2023 Stanford University Convex Optimization Group

