Skip to content

Commit

Permalink
Merge pull request #39 from bbcho/v0286
Browse files Browse the repository at this point in the history
V0286
  • Loading branch information
bbcho authored Dec 7, 2024
2 parents 5697a0e + 55a3fab commit be2c2c5
Show file tree
Hide file tree
Showing 7 changed files with 2,565 additions and 1,774 deletions.
48 changes: 48 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/python
{
"name": "Python 3",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
// "image": "mcr.microsoft.com/devcontainers/python:1-3.10-bookworm",
"image": "bbcho/base:0.0.2",
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"ms-toolsai.jupyter",
"reditorsupport.r"

],
"settings": {
"python.testing.pytestArgs": [
"."
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"python.formatting.provider": "black",
"python.linting.mypyEnabled": true,
"python.linting.enabled": true
}
}
}
//"features": {
// "ghcr.io/rocker-org/devcontainer-features/r-apt:latest": {},
// "ghcr.io/rocker-org/devcontainer-features/quarto-cli:1": {}
//}

// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},

// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],

// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "pip3 install --user -r requirements.txt",

// Configure tool-specific properties.
// "customizations": {},

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

File renamed without changes.
12 changes: 6 additions & 6 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ jobs:
Linux-build:
runs-on: ubuntu-latest
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
steps:
- uses: actions/checkout@v3
- name: build manylinux wheels
Expand All @@ -37,8 +37,8 @@ jobs:
Matrix-build:
runs-on: ${{ matrix.os }}
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
strategy:
matrix:
os: [windows-latest] #, macos-latest]
Expand Down Expand Up @@ -76,8 +76,8 @@ jobs:
pip install setuptools wheel twine cython numpy
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: |
python setup.py sdist
twine upload dist/*
File renamed without changes.
156 changes: 137 additions & 19 deletions src/risktools/_multivariate.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def generate_eps_MV(cor, T, dt, sims=1000, mu=None, seed=None):
return eps


def simGBM_MV(s0, r, sigma, T, dt, mu=None, cor=None, eps=None, sims=1000):
def simGBM_MV(s0, r, sigma, T, dt, mu=None, cor=None, eps=None, sims=1000, seed=None):
"""
Simulate Geometric Brownian Motion for stochastic processes with
multiple assets using a multivariate normal distribution.
Expand Down Expand Up @@ -187,6 +187,8 @@ def simGBM_MV(s0, r, sigma, T, dt, mu=None, cor=None, eps=None, sims=1000):
simulations, and M is the number of assets. By default None.
sims : int
Number of simulations. By default 1000.
seed : int | None
To pass to numpy random number generator as seed. For testing only.
Returns
-------
Expand Down Expand Up @@ -220,7 +222,7 @@ def simGBM_MV(s0, r, sigma, T, dt, mu=None, cor=None, eps=None, sims=1000):
N = int(T / dt)

if eps is None:
eps = generate_eps_MV(cor, T, dt, sims, mu)
eps = generate_eps_MV(cor, T, dt, sims, mu, seed=seed)

s = _np.zeros((N + 1, sims, len(s0)))

Expand All @@ -245,7 +247,7 @@ def simOU_MV(
eps=None,
seed=None,
log_price=False,
**kwargs
**kwargs,
):
"""
Simulate Ornstein-Uhlenbeck process for stochastic processes for
Expand Down Expand Up @@ -352,6 +354,16 @@ def simOU_MV(

s = _np.zeros((N + 1, eps.shape[1], eps.shape[2]))

try:
s0 = s0.to_numpy()
except:
pass

try:
theta = theta.to_numpy()
except:
pass

for i in range(0, eps.shape[2]):
s[:, :, i] = simOU(
s0=s0[i],
Expand All @@ -362,7 +374,7 @@ def simOU_MV(
dt=dt,
eps=eps[:, :, i],
log_price=log_price,
**kwargs
**kwargs,
)

return s
Expand All @@ -385,7 +397,7 @@ def simOUJ_MV(
elp=None,
ejp=None,
seed=None,
**kwargs
**kwargs,
):
"""
Simulate Ornstein-Uhlenbeck Jump process for stochastic processes for
Expand Down Expand Up @@ -535,7 +547,7 @@ def simOUJ_MV(
ejp=ejp_tmp,
seed=seed,
mr_lag=mr_lag[i],
**kwargs
**kwargs,
)

return s
Expand Down Expand Up @@ -1087,12 +1099,65 @@ def fit(self):
self._sigma = returns.std() * _np.sqrt(1 / self._dt)
self._cor = returns.corr()

def simulate(self, sims=1000):
def simulate(self, sims=1000, seed=None):

self._sims = simGBM_MV(
self._s0, self._r, self._sigma, self._T, self._dt, cor=self._cor, sims=sims
self._s0,
self._r,
self._sigma,
self._T,
self._dt,
cor=self._cor,
sims=sims,
seed=seed,
)

def output(self, start_date=None, freq="B", names=None):
"""
Method for outputing the results of the simulations. It will return a
dictionary of dataframes where the names are the column names of the original
pricing dataframe passed to the object if it exists.
Parameters
----------
start_date : str or datetime, optional
Start date of the simulation. By default None.
freq : str, optional
Frequency of the simulation. By default 'B'.
names : list[str], optional
List of strings to use as asset names in the output dictionary. Should be of length N
where N is the number of assets in the portfolio. By default None.
Returns
-------
Dictionary of dataframes where the keys are the column names of the original
pricing dataframe passed to the object if it exists.
"""

df = self._sims.copy()

if (names is None) & (self._prices is not None):
names = self._prices.columns
else:
names = [f"Asset {str(i)}" for i in range(0, df.shape[2])]

if start_date is None:
start_date = _pd.Timestamp.now().floor("D") + _pd.Timedelta(days=1)

dates = _pd.date_range(start=start_date, periods=df.shape[0], freq=freq)

out = _pd.DataFrame()
for i, nm in enumerate(names):
tf = _pd.DataFrame(df[:, :, i], index=dates)
tf["asset"] = nm
tf.index.name = "date"
tf.columns.name = "sims"
tf = tf.reset_index().set_index(["asset", "date"])

out = _pd.concat([out, tf], axis=0)

return out


class MVOU(_MVSIM):
"""
Expand Down Expand Up @@ -1196,12 +1261,17 @@ def fit(self, log_price=False, method="OLS", verbose=False, s0=None):
# Only relevent if prices not passed to constructor
if self._prices is not None:
prices = _pd.DataFrame(self._prices)
returns = (_np.log(prices) - _np.log(prices.shift())).dropna()

# try:
# returns = (_np.log(prices) - _np.log(prices.shift())).dropna()
# except:
returns = prices.diff().dropna()

self._s0 = prices.iloc[-1, :] if s0 is None else s0
self._s0 = _np.array(self._s0)

self._params = fitOU_MV(
prices, self._dt, log_price=log_price, method=method, verbose=verbose
df=prices, dt=self._dt, log_price=log_price, method=method, verbose=verbose
)
self._cor = returns.corr()
else:
Expand All @@ -1214,15 +1284,63 @@ def fit(self, log_price=False, method="OLS", verbose=False, s0=None):
if self._asset_names is not None:
self._params.columns = self._asset_names

def simulate(self, sims=1000):
def simulate(self, sims=1000, seed=None):

self._sims = self._sims = simOU_MV(
self._s0,
self._params.loc["mu", :],
self._params.loc["theta", :],
self._T,
self._dt,
self._params.loc["annualized_sigma", :],
self._cor,
self._sims = simOU_MV(
s0=self._s0,
mu=self._params.loc["mu", :],
theta=self._params.loc["theta", :],
T=self._T,
dt=self._dt,
sigma=self._params.loc["annualized_sigma", :],
cor=self._cor,
sims=sims,
log_price=False,
seed=seed,
)

def output(self, start_date=None, freq="B", names=None):
"""
Method for outputing the results of the simulations. It will return a
dictionary of dataframes where the names are the column names of the original
pricing dataframe passed to the object if it exists.
Parameters
----------
start_date : str or datetime, optional
Start date of the simulation. By default None.
freq : str, optional
Frequency of the simulation. By default 'B'.
names : list[str], optional
List of strings to use as asset names in the output dictionary. Should be of length N
where N is the number of assets in the portfolio. By default None.
Returns
-------
Dictionary of dataframes where the keys are the column names of the original
pricing dataframe passed to the object if it exists.
"""

df = self._sims.copy()

if (names is None) & (self._prices is not None):
names = self._prices.columns
else:
names = [f"Asset {str(i)}" for i in range(0, df.shape[2])]

if start_date is None:
start_date = _pd.Timestamp.now().floor("D") + _pd.Timedelta(days=1)

dates = _pd.date_range(start=start_date, periods=df.shape[0], freq=freq)

out = _pd.DataFrame()
for i, nm in enumerate(names):
tf = _pd.DataFrame(df[:, :, i], index=dates)
tf["asset"] = nm
tf.index.name = "date"
tf.columns.name = "sims"
tf = tf.reset_index().set_index(["asset", "date"])

out = _pd.concat([out, tf], axis=0)

return out
10 changes: 7 additions & 3 deletions src/risktools/_sims.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,17 @@ def is_iterable(x):
def make_into_array(x, N):
# make an array of same size as N+1
if is_iterable(x):
# if isinstance(x, _pd.DataFrame) == False:
# x = _pd.DataFrame(x)
if len(x.shape) == 2:
if isinstance(x, _pd.DataFrame) == False:
x = _pd.DataFrame(x)
if x.shape[1] >= 2:
# if a 2D array is passed, return it as is
# good for stocastic volatility matrix
# but stack first row
x = _np.vstack((x.iloc[0], x))
return x

if x.shape[0] == N:
# stack first row
x = _np.append(x.iloc[0], x)
else:
raise ValueError(
Expand All @@ -48,6 +50,7 @@ def make_into_array(x, N):

else:
x = _np.ones(N + 1) * x


return x

Expand Down Expand Up @@ -223,6 +226,7 @@ def simOU(
mu = _np.tile(_np.array(mu), sims)
else:
mu = mu.flatten("F")


# Don't run if 2D array passed for sigma
if len(sigma.shape) == 1:
Expand Down
Loading

0 comments on commit be2c2c5

Please sign in to comment.