Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

147 replace jupyter with marino #148

Merged
merged 2 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ jupyter: install ## Start jupyter lab
@uv run jupyter lab


.PHONY: marimo
marimo: install ## Start marimo
@uv pip install marimo
@uv run marimo edit marimo
# $(argument)

#.PHONY: boil
#boil: ## Update the boilerplate code
# @poetry run pip install cvxcooker
Expand Down
321 changes: 321 additions & 0 deletions marimo/data/stock_prices.csv

Large diffs are not rendered by default.

112 changes: 112 additions & 0 deletions marimo/demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import marimo

__generated_with = "0.9.27"
app = marimo.App(width="medium")


@app.cell
def __():
import cvxpy as cp
import numpy as np
import pandas as pd

import marimo as mo
from cvx.portfolio.min_risk import minrisk_problem
from cvx.risk.sample import SampleCovariance
from cvx.simulator import Builder

pd.options.plotting.backend = "plotly"
return Builder, SampleCovariance, cp, minrisk_problem, mo, np, pd


@app.cell
def __(pd):
# Load some historic stock prices
prices = pd.read_csv(
"marimo/data/stock_prices.csv", index_col=0, parse_dates=True, header=0
)

# Estimate a series of historic covariance matrices
returns = prices.pct_change().dropna(axis=0, how="all")
return prices, returns


@app.cell
def __(
Builder,
SampleCovariance,
cp,
minrisk_problem,
np,
prices,
returns,
):
cov = returns.ewm(com=60, min_periods=100).cov().dropna(axis=0, how="all")
start = cov.index[0][0]

# Establish a risk model
_risk_model = SampleCovariance(num=20)

# Perform the backtest
_builder = Builder(prices=prices.truncate(before=start), initial_aum=1e6)

_w = cp.Variable(len(_builder.prices.columns))
_problem = minrisk_problem(_risk_model, _w)

for _t, _state in _builder:
_risk_model.update(
cov=cov.loc[_t[-1]].values,
lower_assets=np.zeros(20),
upper_assets=np.ones(20),
)

# don't reconstruct the problem in every iteration!
_problem.solve()

_builder.weights = _w.value
_builder.aum = _state.aum

_portfolio = _builder.build()

_portfolio.nav.plot()
return cov, start


@app.cell
def __(Builder, cp, minrisk_problem, np, prices, returns, start):
from cvx.risk.cvar import CVar

_risk_model = CVar(alpha=0.80, n=40, m=20)

# Perform the backtest
_builder = Builder(prices=prices.truncate(before=start), initial_aum=1e6)

_w = cp.Variable(len(_builder.prices.columns))
_problem = minrisk_problem(_risk_model, _w)

for _t, _state in _builder:
_risk_model.update(
returns=returns.truncate(after=_t[-1]).tail(40).values,
lower_assets=np.zeros(20),
upper_assets=np.ones(20),
)

# don't reconstruct the problem in every iteration!
_problem.solve()

_builder.weights = _w.value
_builder.aum = _state.aum

_portfolio = _builder.build()
_portfolio.nav.plot()

return (CVar,)


@app.cell
def __():
return


if __name__ == "__main__":
app.run()
80 changes: 80 additions & 0 deletions marimo/factormodel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import marimo

__generated_with = "0.9.27"
app = marimo.App(width="medium")


@app.cell
def __():
import cvxpy as cvx
import numpy as np
import pandas as pd

from cvx.portfolio.min_risk import minrisk_problem
from cvx.risk.factor import FactorModel
from cvx.risk.linalg import pca

return FactorModel, cvx, minrisk_problem, np, pca, pd


@app.cell
def __(pd):
prices = pd.read_csv(
"marimo/data/stock_prices.csv", index_col=0, header=0, parse_dates=True
)
returns = prices.pct_change().fillna(0.0)
return prices, returns


@app.cell
def __(pca, returns):
factors = pca(returns=returns, n_components=10)
return (factors,)


@app.cell
def __(FactorModel, factors, np, returns):
model = FactorModel(assets=len(returns.columns), k=10)

# update the model parameters
model.update(
cov=factors.cov,
exposure=factors.exposure.values,
idiosyncratic_risk=factors.idiosyncratic.std().values,
lower_assets=np.zeros(20),
upper_assets=np.ones(20),
lower_factors=-0.1 * np.ones(10),
upper_factors=0.1 * np.ones(10),
)

# test the risk model with uniform weights
weights = 0.05 * np.ones(20)
model.estimate(weights).value
return model, weights


@app.cell
def __(cvx, minrisk_problem, model, np, pd, prices):
w = cvx.Variable(20)
y = cvx.Variable(10)

problem = minrisk_problem(model, w, y=y)
problem.solve()

print(pd.Series(data=w.value, index=prices.columns))
print(model.estimate(w, y=y).value)

# check the solution
assert np.isclose(w.value.sum(), 1.0)
assert np.all(w.value > -0.01)
print(y.value)
return problem, w, y


@app.cell
def __():
return


if __name__ == "__main__":
app.run()
Empty file added marimo/util/__init__.py
Empty file.
56 changes: 56 additions & 0 deletions marimo/util/random.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from __future__ import annotations

import uuid

import numpy as np
import pandas as pd


def random_weights(assets):
"""
Construct a vector of non-negative random weights. Their sum shall be 1
"""
# Get some random weights
weights = pd.Series(index=assets, data=np.random.rand(len(assets)))
return weights / weights.sum()


def random_factors(T, N=2, const_factor=True):
"""
Construct N random factor time series for T timestamps
"""
factors = pd.DataFrame(
index=range(1, T + 1),
columns=[f"F{i}" for i in range(N)],
data=np.random.randn(T, N),
)
# add the constant factor
if const_factor:
factors["const"] = 1
return factors


def random_beta(assets, factors):
"""
Construct a random exposure matrix
"""
data = np.random.randn(factors.shape[1], len(assets))
return pd.DataFrame(columns=assets, index=factors.columns, data=data)


def random_noise(frame):
"""
Construct a frame of random noise with exactly the same dimensions as the input frame
"""
return pd.DataFrame(
columns=frame.columns,
index=frame.index,
data=np.random.randn(frame.shape[0], frame.shape[1]),
)


def random_assets(num):
"""
Construct a vector of random assets
"""
return [str(uuid.uuid4())[:7] for _ in range(num)]
Loading