Skip to content

Commit

Permalink
Merge pull request #269 from cvxgrp/267-add-name-to-nav-series
Browse files Browse the repository at this point in the history
267 add name to nav series
  • Loading branch information
tschm authored Dec 20, 2023
2 parents d1ddb8c + 9b51db4 commit 2930e12
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 168 deletions.
8 changes: 1 addition & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,5 @@ jupyter: install ## Run jupyter lab
@poetry run jupyter lab

.PHONY: marimo
marimo: install ## Run jupyter lab
marimo: install ## Install Marimo
@poetry run pip install marimo
@poetry run marimo edit

.PHONY: boil
boil: ## Update the boilerplate code
@poetry run pip install cvxcooker
@poetry run cook pyproject.toml
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ and initialize the amount of cash used in an experiment:

```python
import pandas as pd
from cvx.simulator import EquityBuilder
from cvx.simulator import Builder

prices = pd.read_csv("prices.csv", index_col=0, parse_dates=True, header=0)
b = EquityBuilder(prices=prices, initial_cash=1e6)
b = Builder(prices=prices, initial_cash=1e6)
```

Prices have to be valid, there may be NaNs only at the beginning and the end of
Expand All @@ -65,6 +65,8 @@ for t, state in b:
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
```

Here t is the growing list of timestamps, e.g. in the first iteration
Expand All @@ -81,6 +83,7 @@ implemenent the popular $1/n$ strategy.
for t, state in b:
# each day we invest a quarter of the capital in the assets
b.position = 0.25 * state.nav / state.prices
b.aum = state.aum
```

Note that we update the position at the last element in the t list
Expand All @@ -91,6 +94,7 @@ The builder class also exposes setters for such alternative conventions.
for t, state in b:
# each day we invest a quarter of the capital in the assets
b.weights = np.ones(4)*0.25
b.aum = state.aum
```

### Build the portfolio
Expand Down
19 changes: 14 additions & 5 deletions cvx/simulator/portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,13 @@ def assets(self) -> pd.Index:
def nav(self):
"""Return a pandas series representing the NAV"""
if isinstance(self.aum, pd.Series):
return self.aum
series = self.aum
else:
profit = (self.cashposition.shift(1) * self.returns.fillna(0.0)).sum(axis=1)
return profit.cumsum() + self.aum
series = profit.cumsum() + self.aum

series.name = "NAV"
return series

@property
def profit(self) -> pd.Series:
Expand All @@ -98,7 +101,9 @@ def profit(self) -> pd.Series:
Returns: pd.Series: A pandas series representing the profit
gained or lost in the portfolio based on changes in asset prices.
"""
return (self.cashposition.shift(1) * self.returns.fillna(0.0)).sum(axis=1)
series = (self.cashposition.shift(1) * self.returns.fillna(0.0)).sum(axis=1)
series.name = "Profit"
return series

@property
def highwater(self) -> pd.Series:
Expand All @@ -115,7 +120,9 @@ def highwater(self) -> pd.Series:
Min_periods argument is set to 1 to include the minimum period of one day.
The resulting series will show the highest value the portfolio has reached at each point in time.
"""
return self.nav.expanding(min_periods=1).max()
series = self.nav.expanding(min_periods=1).max()
series.name = "Highwater"
return series

@property
def drawdown(self) -> pd.Series:
Expand All @@ -132,7 +139,9 @@ def drawdown(self) -> pd.Series:
A positive drawdown means the portfolio is currently worth
less than its high-water mark. A drawdown of 0.1 implies that the nav is currently 0.9 times the high-water mark
"""
return 1.0 - self.nav / self.highwater
series = 1.0 - self.nav / self.highwater
series.name = "Drawdown"
return series

@property
def cashposition(self):
Expand Down
288 changes: 149 additions & 139 deletions poetry.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion tests/test_applications/test_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,6 @@ def test_case(columns, index, prices):
# )
# nav is the net asset value equity and cash, 1) 2000 2) 2000 3) 2000 4) 1000
pd.testing.assert_series_equal(
portfolio.nav, pd.Series(index=index, data=[2000.0, 2000.0, 2000.0, 1000.0])
portfolio.nav,
pd.Series(index=index, data=[2000.0, 2000.0, 2000.0, 1000.0], name="NAV"),
)
8 changes: 6 additions & 2 deletions tests/test_applications/test_portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ def test_from_cash_position_prices(prices):
)

profit = (portfolio.cashposition.shift(1) * portfolio.returns).sum(axis=1)
pd.testing.assert_series_equal(portfolio.nav, profit.cumsum() + portfolio.aum)
pd.testing.assert_series_equal(
portfolio.nav, profit.cumsum() + portfolio.aum, check_names=False
)


def test_from_cash_returns(prices):
Expand All @@ -46,4 +48,6 @@ def test_from_cash_returns(prices):
)

profit = (portfolio.cashposition.shift(1) * portfolio.returns).sum(axis=1)
pd.testing.assert_series_equal(portfolio.nav, profit.cumsum() + portfolio.aum)
pd.testing.assert_series_equal(
portfolio.nav, profit.cumsum() + portfolio.aum, check_names=False
)
2 changes: 1 addition & 1 deletion tests/test_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def test_build_empty(builder, prices):
pd.testing.assert_frame_equal(portfolio.prices, prices)
pd.testing.assert_frame_equal(portfolio.units, np.NaN * prices)
pd.testing.assert_series_equal(
portfolio.profit, pd.Series(index=prices.index, data=0.0)
portfolio.profit, pd.Series(index=prices.index, data=0.0, name="Profit")
)


Expand Down
18 changes: 7 additions & 11 deletions tests/test_portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,6 @@ def portfolio(prices, nav):

return Portfolio(prices=prices, units=units, aum=nav)

# positions = pd.DataFrame(index=prices.index, columns=prices.columns, data=1.0)
# b = EquityBuilder(prices, initial_cash=1e6)

# for t, state in b:
# b.position = positions.loc[t[-1]]

# return b.build()


def test_assets(portfolio, prices):
"""
Expand Down Expand Up @@ -107,11 +99,13 @@ def test_drawdown(portfolio):
:param portfolio: the portfolio object (fixture)
"""
pd.testing.assert_series_equal(
portfolio.highwater, portfolio.nav.expanding(min_periods=1).max()
portfolio.highwater,
portfolio.nav.expanding(min_periods=1).max(),
check_names=False,
)

drawdown = 1.0 - portfolio.nav / portfolio.highwater
pd.testing.assert_series_equal(portfolio.drawdown, drawdown)
pd.testing.assert_series_equal(portfolio.drawdown, drawdown, check_names=False)


def test_monotonic():
Expand Down Expand Up @@ -176,7 +170,9 @@ def test_profit(portfolio):
:param portfolio: the portfolio object (fixture)
"""
pd.testing.assert_series_equal(
portfolio.profit, portfolio.equity.sum(axis=1).diff().fillna(0.0)
portfolio.profit,
portfolio.equity.sum(axis=1).diff().fillna(0.0),
check_names=False,
)


Expand Down

0 comments on commit 2930e12

Please sign in to comment.