Skip to content

Commit 5303269

Browse files
committed
cleanup simOU
1 parent 9d9df23 commit 5303269

File tree

5 files changed

+163
-138
lines changed

5 files changed

+163
-138
lines changed

notebooks/c-code.ipynb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@
1616
"from time import time"
1717
]
1818
},
19+
{
20+
"cell_type": "code",
21+
"execution_count": null,
22+
"id": "847e6fff",
23+
"metadata": {},
24+
"outputs": [],
25+
"source": []
26+
},
1927
{
2028
"cell_type": "code",
2129
"execution_count": 7,

pytest/test_sims.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,29 @@ def test_simOU():
8282
assert np.allclose(df1, df2), "C seed eps test failed"
8383

8484

85+
# test time varying mu
86+
87+
mu = np.ones(16) * 5
88+
89+
rng = Generator(SFC64(seed=12345))
90+
eps = pd.DataFrame(rng.normal(0,1,size=(16, 2)))
91+
92+
df1 = rt.simOU(s0, mu, theta, sigma, T, dt, sims=2, eps=eps, log_price=False, c=False)
93+
df2 = rt.simOU(s0, mu, theta, sigma, T, dt, sims=2, seed=12345, log_price=False, c=False)
94+
assert np.allclose(df1, df2), "Py time varying mu test failed"
95+
96+
# rng = default_rng(seed=12345)
97+
rng = Generator(SFC64(seed=12345))
98+
eps = rng.normal(0,1,size=17*2)
99+
eps = eps.reshape((2,17)).T
100+
eps = pd.DataFrame(eps).iloc[1:,:]
101+
102+
df1 = rt.simOU(s0, mu, theta, sigma, T, dt, sims=2, eps=eps, log_price=False, c=True)
103+
df2 = rt.simOU(s0, mu, theta, sigma, T, dt, sims=2, seed=12345, log_price=False, c=True)
104+
assert np.allclose(df1, df2), "C time varying mu test failed"
105+
106+
107+
85108

86109
if __name__ == "__main__":
87110
test_simOU()

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def get_ext_filename(self, ext_name):
4848

4949
setuptools.setup(
5050
name="risktools",
51-
version="0.2.7",
51+
version="0.2.7.1",
5252
author="Ben Cho",
5353
license="gpl-3.0", # Chose a license from here: https://help.github.com/articles/licensing-a-repository
5454
author_email="[email protected]",

src/risktools/_sims.py

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ def simOU(s0=5, mu=4, theta=2, sigma=1, T=1, dt=1 / 252, sims=1000, eps=None, se
119119
Starting value for mean reverting random walk at time = 0
120120
mu : float, int or pandas Series
121121
Mean that the function will revert to. Can be either a scalar value (i.e. 5) or a pandas series for a
122-
time dependent mean. If array-like, it must be the same length as T/dt (i.e. the number of periods)
122+
time dependent mean. If array-like, it must be the same length as T/dt (i.e. the number of periods,
123+
excluding start value)
123124
theta : float
124125
Mean reversion rate, higher number means it will revert slower
125126
sigma : float
@@ -157,10 +158,26 @@ def simOU(s0=5, mu=4, theta=2, sigma=1, T=1, dt=1 / 252, sims=1000, eps=None, se
157158

158159
# number of business days in a year
159160
bdays_in_year = 252
161+
162+
# calc periods
163+
N = int(T/dt)
160164

161165
# print half-life of theta
162166
print("Half-life of theta in days = ", _np.log(2) / theta * bdays_in_year)
163167

168+
# make mu an array of same size as N+1
169+
try:
170+
iter(mu)
171+
if len(mu) == N:
172+
mu = _np.append(mu[0] , mu)
173+
else:
174+
raise ValueError("if mu is passed as an iterable, it must be of length int(T/dt)")
175+
except:
176+
mu = _np.ones(N+1)*mu
177+
178+
# make same size as 1D array of all periods and sims
179+
mu = _np.tile(_np.array(mu), sims)
180+
164181
if c == True:
165182
return _simOUc(s0=s0, mu=mu, theta=theta, T=T, dt=dt, sigma=sigma, sims=sims, eps=eps, seed=seed, log_price=log_price)
166183
else:
@@ -183,17 +200,6 @@ def _simOUc(s0, theta, mu, dt, sigma, T, sims=10, eps=None, seed=None, log_price
183200
'''
184201
)
185202

186-
# make mu an array of same size as N+1
187-
try:
188-
iter(mu)
189-
if len(mu) != N+1:
190-
raise ValueError("if mu is passed as an iterable, it must be of length int(T/dt) + 1 to account for starting value s0")
191-
except:
192-
mu = _np.ones((N+1))*mu
193-
194-
# make same size as 1D array of all periods and sims
195-
mu = _np.tile(_np.array(mu), sims)
196-
197203
# generate a 1D array of random numbers that is based on a
198204
# 2D array of size P x S where P is the number of time steps
199205
# including s0 (so N + 1) and S is the number of sims. 1D
@@ -216,25 +222,18 @@ def _simOUc(s0, theta, mu, dt, sigma, T, sims=10, eps=None, seed=None, log_price
216222

217223

218224
def _simOUpy(s0, mu, theta, sigma, T, dt, sims=1000, eps=None, seed=None, log_price=False):
219-
225+
mu = _pd.Series(mu)
226+
220227
# number of periods dt in T
221228
periods = int(T / dt)
222229

223-
if isinstance(mu, list):
224-
assert len(mu) == (
225-
periods
226-
), "Time dependent mu used, but the length of mu is not equal to the number of periods calculated."
227-
228230
# init df with zeros, rows are steps forward in time, columns are simulations
229231
out = _np.zeros((periods + 1, sims))
230232
out = _pd.DataFrame(data=out)
231233

232234
# set first row as starting value of sim
233235
out.loc[0, :] = s0
234236

235-
if isinstance(mu, list):
236-
mu = _pd.Series(mu)
237-
238237
# calc gaussian vector
239238
if eps is None:
240239
# rng = default_rng(seed)
@@ -251,18 +250,13 @@ def _simOUpy(s0, mu, theta, sigma, T, dt, sims=1000, eps=None, seed=None, log_pr
251250
continue # skip first row
252251

253252
# calc step
254-
if isinstance(mu, list) | isinstance(mu, _pd.Series):
255-
out.iloc[i, :] = (
256-
out.iloc[i - 1, :]
257-
+ (theta * (mu.iloc[i - 1] - out.iloc[i - 1, :]) - ss) * dt
258-
+ sigma * out.iloc[i, :] * _np.sqrt(dt)
259-
)
260-
else:
261-
out.iloc[i, :] = (
262-
out.iloc[i - 1, :]
263-
+ (theta * (mu - out.iloc[i - 1, :]) - ss)* dt
264-
+ sigma * out.iloc[i, :] * _np.sqrt(dt)
265-
)
253+
254+
out.iloc[i, :] = (
255+
out.iloc[i - 1, :]
256+
+ (theta * (mu.iloc[i] - out.iloc[i - 1, :]) - ss) * dt
257+
+ sigma * out.iloc[i, :] * _np.sqrt(dt)
258+
)
259+
266260

267261
return out
268262

0 commit comments

Comments
 (0)