-
Notifications
You must be signed in to change notification settings - Fork 574
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
Heston analytics #30
Heston analytics #30
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, Fabrice! Sorry for the slow review.
http://faculty.baruch.cuny.edu/lwu/890/Heston93.pdf | ||
""" | ||
|
||
def heston_price(*, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please create a folder in models
and call it heston
, like we did for hull white. Then move the heston model into that folder. Inside heston
you should create approximations
folder like we did for Black-Scholes (see here). You should put this file there. Thanks!
Also, should we call this function eu_option_price
? User then can access it via tff.models.heston.approximations.eu_option_price
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, I have renamed module/folder accordingly
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
too many blank lines. One is enough
thetas=None, | ||
sigmas=None, | ||
rhos=None, | ||
variances=None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please make the arguments in a similar order as in option_price
for consistency? I.e., move variance
to the top and forwards right after spots?
Also, is it possible to add discount_rates and dividend yield?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have reordered arguments a little bit, but may not be exactly what you were asking. I think in general it makes sense to group on one side the option contract parameters together and on the other side the model parameters. But agree with you that keeping consistency with other functions like the black-scholes option price needs to be kept in mind.
I have added spots, discount rates, continuous dividends like in black scholes option prices, but duplicating a lot of code along the way, so I am not sure if the extra flexibility is worth it. I guess moving the spot/discount factor/dividends/discount rate/... to a single place would help, and ideally make it object oriented, but that depends on how the code is going to be used in practise, so I can't comment on that.
) | ||
|
||
# Expected print output of prices: | ||
# [24.82219619] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the result not of the same shape as kappas?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point, the docstring was incorrect
return tf.math.exp(C + D * variances_real) | ||
|
||
def integrand_function(u, k): | ||
""" Note that with Attari, integrand is in 1 / u**2, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[2] instead of Attari
""" WARNING ABOUT CURRENT NUMERICAL ISSUES WITH CURRENT INTEGRATION METHOD | ||
PLEASE READ: | ||
|
||
The integral should be performed from 0 to +infty if we could integrate | ||
with a tensorflow equivalent of scipy.integrate.quad, but this isn't | ||
currently available: | ||
- lower bound has to be strictly positive as integrand not defined in 0 as | ||
current integration method can't deal with that | ||
- upper bound has to be finite as current integration method can't deal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be part of the docstring
theta=None, | ||
sigma=None, | ||
rho=None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please rename to kappas, thetas, .. for consistency
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be consistent with the other function, but wouldn't reflect what that function is doing though as that one is not vectorized. Wouldn't it better to leave like that?
for strike in strikes: | ||
for discount_factor in discount_factors: | ||
tff_prices = self.evaluate( | ||
tff.volatility.heston_price( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't heston_price vectorized?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, it is, but not the testing function, so I figured it was easier to do the test like that
import numpy as np | ||
import tensorflow.compat.v2 as tf | ||
from absl.testing import parameterized | ||
from scipy import integrate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please avoid using scipy as it fails some of the internal tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, there may be other python packages that can do that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We had some issues with scipy before and made a decision to not include it as a dependency unless it is needed inside the library itself.
The test should be for the code inside our library and not for scipy. You should use scipy to get the expected result and test the output against that. See, e.g., conjugate gradient test:
argmin = [ |
Here the minimum value was computed using scipy but it is not computed inside the test.
import tf_quant_finance as tff | ||
|
||
|
||
def get_heston_prices(kappa=None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead, compute the correct prices and test for those.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes obviously... but, for that we'll need to wait to have an adaptive gaussian quadrature implemented for performing integrals in tensorflow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please see the comment above.
f9ab9e2
to
54475b8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, Fabrice!
Just a few more minor comments
variances=None, | ||
kappas=None, | ||
thetas=None, | ||
sigmas=None, | ||
rhos=None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to move model parameters to the top?
'may be supplied') | ||
|
||
|
||
with tf.compat.v1.name_scope(name, default_name='eu_option_price'): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we are importing TF2, this one can be replaced with
tf.name_scope(name or 'eu_option_price')
"""Using 'second formula' for the (first) characteristic function of | ||
log( spot_T / forwards ) | ||
(noted 'phi_2' in 'The Little Heston Trap', (Albrecher)) | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This docstring is a bit hard to understand. What is 'second formula'? Also, [1] instead or Albrecher.
Also, the docstring should start with a single line followed by a blank space with args description. Since, this is an internal function, the easy way is to have a 1-line dosctring and then a comment about what you are trying to achieve.
def char_fun(u):
"""Characteristic function of `log( spot_T / forwards )`."""\
# The characteristic function is given in [1]. The notations are as in the paper.
a = kappas_real * thetas_real | ||
h = g * tf.math.exp(-d * expiries_real) | ||
m = 2 * tf.math.log((1 - h) / (1 - g)) | ||
C = (a / sigmas_real ** 2) * ((kappas_real - s - d) * expiries_real - m) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
usually we are not allowed to use 1-symbol variables unless they are the same as in the reference paper. So I trust you this is the case.
Unfortunately, you can't use capital letters for variable names (e.g., "C" or "D"). Please come up with a sensible replacement
if __name__ == "__main__": | ||
import os | ||
|
||
os.environ["CUDA_VISIBLE_DEVICES"] = "-1" | ||
|
||
import numpy as np | ||
prices = eu_option_price( | ||
variances=np.asarray([[0.11]]), | ||
strikes=np.asarray([[102.0]]), | ||
expiries=np.asarray([[1.2]]), | ||
forwards=np.asarray([[100.0]]), | ||
is_call_options=np.asarray([True], dtype=np.bool), | ||
kappas=np.asarray([[2.0]]), | ||
thetas=np.asarray([[0.5]]), | ||
sigmas=np.asarray([[0.15]]), | ||
rhos=np.asarray([[0.3]]), | ||
discount_factors=np.asarray([[1.0]]), | ||
) | ||
print(prices) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume this is a debugging leftover? Please remove
import os | ||
os.environ["CUDA_VISIBLE_DEVICES"] = "-1" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is needed
for kappa in kappas: | ||
for theta in thetas: | ||
for sigma in sigmas: | ||
for rho in rhos: | ||
for v0 in v0s: | ||
for forward in forwards: | ||
for expiriy in expiries: | ||
for strike in strikes: | ||
for discount_factor in discount_factors: | ||
tff_prices = self.evaluate( | ||
tff.models.heston.approximations.eu_option_price( | ||
kappas=np.asarray([kappa]), | ||
thetas=np.asarray([theta]), | ||
sigmas=np.asarray([sigma]), | ||
rhos=np.asarray([rho]), | ||
variances=np.asarray([v0]), | ||
forwards=np.asarray([forward]), | ||
expiries=np.asarray([expiriy]), | ||
strikes=np.asarray([strike]), | ||
discount_factors=np.asarray([discount_factor]), | ||
is_call_options=np.asarray([True, False], dtype=np.bool) | ||
)) | ||
|
||
params = { | ||
"kappa": kappa, | ||
"theta": theta, | ||
"sigma": sigma, | ||
"rho": rho, | ||
"v0": v0, | ||
"forward": forward, | ||
"expiry": expiriy, | ||
"strike": strike, | ||
"discount_factor": discount_factor, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a bit dense. I have addressed our previous conversation. Could you please remove get_heston_prices
and just hardcode the exact answer?
As for me, computing tff.models.heston.approximations.eu_option_price(kappas=kappas, ..)
is better (making sure that the inputs are broadcastable). It also checks that the vectorization is supported.
@@ -74,13 +74,15 @@ def _ensure_tf_install(): # pylint: disable=g-statement-before-imports | |||
from tf_quant_finance import math | |||
from tf_quant_finance import models | |||
from tf_quant_finance import rates | |||
from tf_quant_finance import volatility |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure this module exists?
This reverts commit 6130a23.
I have added Heston semi-analytical formula for European options.
I've put the analytics in tf_quant_finance/volatility/heston_approximation.py as discussed in #9. But contrary to what the path name suggests this isn't an approximation (this is exact) and this doesn't calculate volatility (it calculates prices, but volatility could certainly be backed out from the prices using a solver).
The Fourrier transform is done using tff.math.integration.integrate, which relies on Simpsons integration, which can lead to significant numerical error (when compared to scipy.integrate.quad). The unittest passes with the current tested parameters. But it would fail for different set of parameters (ex. non zero correlation). Better numerical integration would be needed in tensorflow, and the Heston analytics provides an interesting test case (integral of a function from zero to +infty which diverges in zero).