affidiff 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- affidiff/__init__.py +13 -0
- affidiff/helper_functions.py +395 -0
- affidiff/model_cir.py +68 -0
- affidiff/model_ct.py +632 -0
- affidiff/model_gbm.py +349 -0
- affidiff/model_generic.py +490 -0
- affidiff/model_heston.py +322 -0
- affidiff/model_vasicek.py +72 -0
- affidiff/param_cir.py +140 -0
- affidiff/param_ct.py +352 -0
- affidiff/param_gbm.py +133 -0
- affidiff/param_generic.py +155 -0
- affidiff/param_heston.py +321 -0
- affidiff/param_vasicek.py +138 -0
- affidiff/simulate.pyx +77 -0
- affidiff/symbolic.py +53 -0
- affidiff-0.1.0.dist-info/METADATA +18 -0
- affidiff-0.1.0.dist-info/RECORD +20 -0
- affidiff-0.1.0.dist-info/WHEEL +4 -0
- affidiff-0.1.0.dist-info/licenses/LICENSE.md +22 -0
affidiff/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .param_generic import *
|
|
2
|
+
from .param_gbm import *
|
|
3
|
+
from .param_vasicek import *
|
|
4
|
+
from .param_cir import *
|
|
5
|
+
from .param_heston import *
|
|
6
|
+
from .param_ct import *
|
|
7
|
+
|
|
8
|
+
from .model_generic import *
|
|
9
|
+
from .model_gbm import *
|
|
10
|
+
from .model_vasicek import *
|
|
11
|
+
from .model_cir import *
|
|
12
|
+
from .model_heston import *
|
|
13
|
+
from .model_ct import *
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Helper functions
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import print_function, division
|
|
8
|
+
|
|
9
|
+
import time
|
|
10
|
+
import contextlib
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
import matplotlib.pylab as plt
|
|
14
|
+
import seaborn as sns
|
|
15
|
+
import itertools as it
|
|
16
|
+
|
|
17
|
+
from statsmodels.tsa.tsatools import lagmat
|
|
18
|
+
|
|
19
|
+
__all__ = ['nice_errors', 'ajd_drift', 'ajd_diff',
|
|
20
|
+
'plot_trajectories', 'plot_final_distr', 'plot_realized',
|
|
21
|
+
'columnwise_prod', 'rolling_window', 'poly_coef', 'instruments']
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def ajd_drift(state, theta):
|
|
25
|
+
"""Instantaneous mean.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
state : (nsim, nvars) array_like
|
|
30
|
+
Current value of the process
|
|
31
|
+
theta : parameter instance
|
|
32
|
+
Model parameter
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
(nsim, nvars) array_like
|
|
37
|
+
Value of the drift
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
state = np.atleast_2d(state)
|
|
41
|
+
return theta.mat_k0 + state.dot(np.transpose(theta.mat_k1))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def ajd_diff(state, theta):
|
|
45
|
+
"""Instantaneous volatility.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
state : (nsim, nvars) array_like
|
|
50
|
+
Current value of the process
|
|
51
|
+
theta : parameter instance
|
|
52
|
+
Model parameter
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
(nsim, nvars, nvars) array_like
|
|
57
|
+
Value of the diffusion
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
state = np.atleast_2d(state)
|
|
61
|
+
mat_h1 = np.atleast_3d(theta.mat_h1)
|
|
62
|
+
# (nsim, nvars, nvars)
|
|
63
|
+
var = theta.mat_h0 + np.tensordot(state, mat_h1, axes=(1, 0))
|
|
64
|
+
try:
|
|
65
|
+
return np.linalg.cholesky(var)
|
|
66
|
+
except(np.linalg.LinAlgError):
|
|
67
|
+
return np.ones_like(var) * 1e10
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def nice_errors(errors, sdim):
|
|
71
|
+
"""Normalize the errors and apply antithetic sampling.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
errors : array
|
|
76
|
+
Innovations to be standardized
|
|
77
|
+
sdim : int
|
|
78
|
+
Which dimension corresponds to simulation instances?
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
errors : array
|
|
83
|
+
Standardized innovations
|
|
84
|
+
|
|
85
|
+
"""
|
|
86
|
+
if errors.shape[sdim] > 10:
|
|
87
|
+
errors -= errors.mean(sdim, keepdims=True)
|
|
88
|
+
errors /= errors.std(sdim, keepdims=True)
|
|
89
|
+
return np.concatenate((errors, -errors), axis=sdim)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def plot_trajectories(paths, nsub, names):
|
|
93
|
+
"""Plot process realizations.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
paths : array
|
|
98
|
+
Process realizations. Shape is either (nobs,) or (nobs, nsim)
|
|
99
|
+
nsub : int
|
|
100
|
+
Number of subintervals inside of unit interval
|
|
101
|
+
names : str or list of strings
|
|
102
|
+
Labels
|
|
103
|
+
|
|
104
|
+
"""
|
|
105
|
+
if isinstance(paths, list):
|
|
106
|
+
for path, name in zip(paths, names):
|
|
107
|
+
x = np.arange(0, path.shape[0] / nsub, 1 / nsub)
|
|
108
|
+
plt.plot(x, path, label=name)
|
|
109
|
+
else:
|
|
110
|
+
x = np.arange(0, paths.shape[0] / nsub, 1 / nsub)
|
|
111
|
+
plt.plot(x, paths, label=names)
|
|
112
|
+
|
|
113
|
+
plt.xlabel('$t$')
|
|
114
|
+
plt.ylabel('$x_t$')
|
|
115
|
+
plt.legend()
|
|
116
|
+
plt.show()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def plot_final_distr(paths, names):
|
|
120
|
+
"""Plot marginal distribution of the process.
|
|
121
|
+
|
|
122
|
+
Parameters
|
|
123
|
+
----------
|
|
124
|
+
paths : array
|
|
125
|
+
Process realizations. Shape is (nobs, nsim)
|
|
126
|
+
names : str or list of strings
|
|
127
|
+
Labels
|
|
128
|
+
|
|
129
|
+
"""
|
|
130
|
+
if isinstance(paths, list):
|
|
131
|
+
for path, name in zip(paths, names):
|
|
132
|
+
if path.ndim != 2:
|
|
133
|
+
raise ValueError('Simulate more paths!')
|
|
134
|
+
sns.kdeplot(path[-1], label=name)
|
|
135
|
+
else:
|
|
136
|
+
sns.kdeplot(paths[-1], label=names)
|
|
137
|
+
|
|
138
|
+
plt.xlabel('x')
|
|
139
|
+
plt.legend()
|
|
140
|
+
plt.show()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def plot_realized(returns, rvar, suffix=None):
|
|
144
|
+
"""Plot realized returns and volatility.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
returns : array
|
|
149
|
+
Returns
|
|
150
|
+
rvar : array
|
|
151
|
+
Realized variance
|
|
152
|
+
|
|
153
|
+
"""
|
|
154
|
+
fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(7, 6))
|
|
155
|
+
if isinstance(returns, list):
|
|
156
|
+
returns = np.vstack(returns).T
|
|
157
|
+
rlabel = ['Returns ' + x for x in suffix]
|
|
158
|
+
else:
|
|
159
|
+
rlabel = 'Returns'
|
|
160
|
+
if isinstance(rvar, list):
|
|
161
|
+
rvar = np.vstack(rvar).T
|
|
162
|
+
vlabel = ['Realized volatility ' + x for x in suffix]
|
|
163
|
+
else:
|
|
164
|
+
vlabel = 'Realized volatility'
|
|
165
|
+
axes[0].plot(returns)
|
|
166
|
+
axes[1].plot(rvar**.5)
|
|
167
|
+
axes[0].legend(rlabel)
|
|
168
|
+
axes[1].legend(vlabel)
|
|
169
|
+
plt.show()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def columnwise_prod(left, right):
|
|
173
|
+
"""Columnwise kronker product.
|
|
174
|
+
|
|
175
|
+
Parameters
|
|
176
|
+
----------
|
|
177
|
+
left : (n, m) array
|
|
178
|
+
right : (n, p) array
|
|
179
|
+
|
|
180
|
+
Returns
|
|
181
|
+
-------
|
|
182
|
+
(n, m*p) array
|
|
183
|
+
[left * right[:, 0], ..., left * right[:, -1]]
|
|
184
|
+
|
|
185
|
+
Example
|
|
186
|
+
-------
|
|
187
|
+
.. doctest::
|
|
188
|
+
|
|
189
|
+
>>> left = np.arange(6).reshape((3,2))
|
|
190
|
+
>>> left
|
|
191
|
+
array([[0, 1],
|
|
192
|
+
[2, 3],
|
|
193
|
+
[4, 5]])
|
|
194
|
+
>>> right = np.arange(9).reshape((3,3))
|
|
195
|
+
>>> right
|
|
196
|
+
array([[0, 1, 2],
|
|
197
|
+
[3, 4, 5],
|
|
198
|
+
[6, 7, 8]])
|
|
199
|
+
>>> columnwise_prod(left, right)
|
|
200
|
+
array([[ 0, 0, 0, 1, 0, 2],
|
|
201
|
+
[ 6, 9, 8, 12, 10, 15],
|
|
202
|
+
[24, 30, 28, 35, 32, 40]])
|
|
203
|
+
|
|
204
|
+
"""
|
|
205
|
+
prod = left[:, np.newaxis, :] * right[:, :, np.newaxis]
|
|
206
|
+
return prod.reshape((left.shape[0], left.shape[1] * right.shape[1]))
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def rolling_window(fun, mat, window=1):
|
|
210
|
+
"""Rolling window apply.
|
|
211
|
+
|
|
212
|
+
Source: http://www.rigtorp.se/2011/01/01/rolling-statistics-numpy.html
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
fun : function
|
|
217
|
+
Function to apply
|
|
218
|
+
mat : array_like
|
|
219
|
+
Data to transform
|
|
220
|
+
window : int
|
|
221
|
+
Window size
|
|
222
|
+
axis : int
|
|
223
|
+
Which axis to apply to
|
|
224
|
+
|
|
225
|
+
Returns
|
|
226
|
+
-------
|
|
227
|
+
array
|
|
228
|
+
|
|
229
|
+
Examples
|
|
230
|
+
--------
|
|
231
|
+
.. doctest::
|
|
232
|
+
|
|
233
|
+
>>> mat = np.arange(10).reshape((2,5))
|
|
234
|
+
>>> mat
|
|
235
|
+
array([[0, 1, 2, 3, 4],
|
|
236
|
+
[5, 6, 7, 8, 9]])
|
|
237
|
+
>>> rolling_window(np.mean, mat, window=2)
|
|
238
|
+
array([[ 0.5, 1.5, 2.5, 3.5],
|
|
239
|
+
[ 5.5, 6.5, 7.5, 8.5]])
|
|
240
|
+
|
|
241
|
+
"""
|
|
242
|
+
shape = mat.shape[:-1] + (mat.shape[-1] - window + 1, window)
|
|
243
|
+
strides = mat.strides + (mat.strides[-1],)
|
|
244
|
+
mat = np.lib.stride_tricks.as_strided(mat, shape=shape, strides=strides)
|
|
245
|
+
return np.apply_along_axis(fun, -1, mat)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def poly_coef(roots):
|
|
249
|
+
"""Ploynomial coefficients.
|
|
250
|
+
|
|
251
|
+
Parameters
|
|
252
|
+
----------
|
|
253
|
+
roots : list of floats
|
|
254
|
+
Roots of the polynomial, i.e. [a0, a1] for (1 - a0 * x) * (1 - a1 * x)
|
|
255
|
+
|
|
256
|
+
Returns
|
|
257
|
+
-------
|
|
258
|
+
coefs : list of floats
|
|
259
|
+
List of all polynomial coefficients, i.e. [p0, p1, p2]
|
|
260
|
+
with p0 = 1, p1 = - a0 - a1, p2 = a0*a1
|
|
261
|
+
|
|
262
|
+
Examples
|
|
263
|
+
--------
|
|
264
|
+
.. doctest::
|
|
265
|
+
|
|
266
|
+
>>> poly_coef([2, 3])
|
|
267
|
+
[1, -5, 6]
|
|
268
|
+
|
|
269
|
+
>>> poly_coef([2, 3, 4])
|
|
270
|
+
[1, -9, 26, -24]
|
|
271
|
+
|
|
272
|
+
"""
|
|
273
|
+
roots = np.array(roots)
|
|
274
|
+
nroots = roots.size
|
|
275
|
+
coefs = [1]
|
|
276
|
+
for power in range(nroots):
|
|
277
|
+
comb = it.combinations(range(nroots), power+1)
|
|
278
|
+
temp = [np.prod(roots[[x]]) for x in comb]
|
|
279
|
+
coefs.append((-1)**(power+1) * np.sum(temp))
|
|
280
|
+
return coefs
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def instruments(data=None, instrlag=1, nobs=None, instr_choice='const'):
|
|
284
|
+
"""Create an array of instruments.
|
|
285
|
+
|
|
286
|
+
Parameters
|
|
287
|
+
----------
|
|
288
|
+
data : (ninstr, nobs) array
|
|
289
|
+
Returns and realized variance
|
|
290
|
+
instrlag : int
|
|
291
|
+
Number of lags for the instruments
|
|
292
|
+
nobs : int
|
|
293
|
+
Number of observations in the data to match
|
|
294
|
+
instr_choice : str {'const', 'var'}
|
|
295
|
+
Choice of instruments.
|
|
296
|
+
- 'const' : just a constant (unconditional moments)
|
|
297
|
+
- 'var' : lags of instrument data
|
|
298
|
+
|
|
299
|
+
Returns
|
|
300
|
+
-------
|
|
301
|
+
(nobs, ninstr*instrlag + 1) array
|
|
302
|
+
Instrument array
|
|
303
|
+
|
|
304
|
+
Examples
|
|
305
|
+
--------
|
|
306
|
+
.. doctest::
|
|
307
|
+
|
|
308
|
+
>>> instruments(nobs=3)
|
|
309
|
+
array([[ 1.],
|
|
310
|
+
[ 1.],
|
|
311
|
+
[ 1.]])
|
|
312
|
+
|
|
313
|
+
>>> data = np.arange(6).reshape((2,3))
|
|
314
|
+
>>> instruments(data=data)
|
|
315
|
+
array([[ 1.],
|
|
316
|
+
[ 1.],
|
|
317
|
+
[ 1.]])
|
|
318
|
+
>>> instruments(data=data, instr_choice='var')
|
|
319
|
+
array([[ 1., 0., 0.],
|
|
320
|
+
[ 1., 0., 3.],
|
|
321
|
+
[ 1., 1., 4.]])
|
|
322
|
+
>>> instruments(data=data, instr_choice='var', instrlag=2)
|
|
323
|
+
array([[ 1., 0., 0., 0., 0.],
|
|
324
|
+
[ 1., 0., 3., 0., 0.],
|
|
325
|
+
[ 1., 1., 4., 0., 3.]])
|
|
326
|
+
|
|
327
|
+
"""
|
|
328
|
+
if data is not None:
|
|
329
|
+
nobs = data.shape[-1]
|
|
330
|
+
|
|
331
|
+
if instr_choice == 'const' or data is None:
|
|
332
|
+
if nobs is None:
|
|
333
|
+
raise ValueError('Specify nobs!')
|
|
334
|
+
return np.ones((nobs, 1))
|
|
335
|
+
|
|
336
|
+
else:
|
|
337
|
+
instr = lagmat(np.atleast_2d(data).T, maxlag=instrlag)
|
|
338
|
+
width = ((0, 0), (1, 0))
|
|
339
|
+
return np.pad(instr, width, mode='constant', constant_values=1)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def format_time(t):
|
|
343
|
+
"""Format time for nice printing.
|
|
344
|
+
|
|
345
|
+
Parameters
|
|
346
|
+
----------
|
|
347
|
+
t : float
|
|
348
|
+
Time in seconds
|
|
349
|
+
|
|
350
|
+
Returns
|
|
351
|
+
-------
|
|
352
|
+
format template
|
|
353
|
+
|
|
354
|
+
"""
|
|
355
|
+
if t > 60 or t == 0:
|
|
356
|
+
units = 'min'
|
|
357
|
+
t /= 60
|
|
358
|
+
elif t > 1:
|
|
359
|
+
units = 's'
|
|
360
|
+
elif t > 1e-3:
|
|
361
|
+
units = 'ms'
|
|
362
|
+
t *= 1e3
|
|
363
|
+
elif t > 1e-6:
|
|
364
|
+
units = 'us'
|
|
365
|
+
t *= 1e6
|
|
366
|
+
else:
|
|
367
|
+
units = 'ns'
|
|
368
|
+
t *= 1e9
|
|
369
|
+
return '%.1f %s' % (t, units)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
@contextlib.contextmanager
|
|
373
|
+
def take_time(desc):
|
|
374
|
+
"""Context manager for timing the code.
|
|
375
|
+
|
|
376
|
+
Parameters
|
|
377
|
+
----------
|
|
378
|
+
desc : str
|
|
379
|
+
Description of the code
|
|
380
|
+
|
|
381
|
+
Example
|
|
382
|
+
-------
|
|
383
|
+
>>> with take_time('Estimation'):
|
|
384
|
+
>>> estimate()
|
|
385
|
+
|
|
386
|
+
"""
|
|
387
|
+
t0 = time.time()
|
|
388
|
+
yield
|
|
389
|
+
dt = time.time() - t0
|
|
390
|
+
print('%s took %s' % (desc, format_time(dt)))
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
if __name__ == "__main__":
|
|
394
|
+
import doctest
|
|
395
|
+
doctest.testmod()
|
affidiff/model_cir.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
CIR model class
|
|
5
|
+
~~~~~~~~~~~~~~~
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import print_function, division
|
|
9
|
+
|
|
10
|
+
from .model_generic import SDE
|
|
11
|
+
|
|
12
|
+
__all__ = ['CIR']
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CIR(SDE):
|
|
16
|
+
|
|
17
|
+
"""Cox-Ingersoll-Ross (CIR) model.
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, param=None):
|
|
22
|
+
"""Initialize the class.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
param : parameter instance
|
|
27
|
+
True parameters used for simulation of the data
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
super(CIR, self).__init__(param)
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def drift(state, theta):
|
|
34
|
+
"""Drift function.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
state : (nvars, nsim) array_like
|
|
39
|
+
Current value of the process
|
|
40
|
+
theta : parameter instance
|
|
41
|
+
Model parameter
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
scalar
|
|
46
|
+
Drift value
|
|
47
|
+
|
|
48
|
+
"""
|
|
49
|
+
return theta.kappa * (theta.mean - state)
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def diff(state, theta):
|
|
53
|
+
"""Diffusion (instantaneous volatility) function.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
state : (nvars, nsim) array_like
|
|
58
|
+
Current value of the process
|
|
59
|
+
theta : parameter instance
|
|
60
|
+
Model parameter
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
scalar
|
|
65
|
+
Diffusion value
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
return theta.eta * state**.5
|