sdevpy 1.0.1__tar.gz → 1.0.3__tar.gz

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.
Files changed (58) hide show
  1. {sdevpy-1.0.1/src/sdevpy.egg-info → sdevpy-1.0.3}/PKG-INFO +2 -1
  2. {sdevpy-1.0.1 → sdevpy-1.0.3}/pyproject.toml +2 -2
  3. sdevpy-1.0.3/src/sdevpy/__init__.py +1 -0
  4. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/machinelearning/callbacks.py +36 -14
  5. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/machinelearning/learningmodel.py +6 -2
  6. sdevpy-1.0.3/src/sdevpy/machinelearning/learningschedules.py +78 -0
  7. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/machinelearning/topology.py +1 -1
  8. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/maths/metrics.py +11 -0
  9. sdevpy-1.0.3/src/sdevpy/maths/optimization.py +158 -0
  10. sdevpy-1.0.3/src/sdevpy/projects/stovolinverse/stovolinvgen.py +75 -0
  11. sdevpy-1.0.3/src/sdevpy/projects/stovolinverse/stovolinvtrain.py +369 -0
  12. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/settings.py +4 -2
  13. sdevpy-1.0.3/src/sdevpy/test.py +477 -0
  14. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/tools/constants.py +3 -0
  15. sdevpy-1.0.3/src/sdevpy/tools/utils.py +7 -0
  16. sdevpy-1.0.3/src/sdevpy/volsurfacegen/sabrgenerator.py +481 -0
  17. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/volsurfacegen/smilegenerator.py +67 -1
  18. {sdevpy-1.0.1 → sdevpy-1.0.3/src/sdevpy.egg-info}/PKG-INFO +2 -1
  19. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy.egg-info/SOURCES.txt +3 -0
  20. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy.egg-info/requires.txt +1 -0
  21. sdevpy-1.0.1/src/sdevpy/__init__.py +0 -1
  22. sdevpy-1.0.1/src/sdevpy/machinelearning/learningschedules.py +0 -23
  23. sdevpy-1.0.1/src/sdevpy/maths/optimization.py +0 -1
  24. sdevpy-1.0.1/src/sdevpy/test.py +0 -301
  25. sdevpy-1.0.1/src/sdevpy/volsurfacegen/sabrgenerator.py +0 -227
  26. {sdevpy-1.0.1 → sdevpy-1.0.3}/LICENSE +0 -0
  27. {sdevpy-1.0.1 → sdevpy-1.0.3}/README.md +0 -0
  28. {sdevpy-1.0.1 → sdevpy-1.0.3}/setup.cfg +0 -0
  29. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/analytics/bachelier.py +0 -0
  30. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/analytics/black.py +0 -0
  31. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/analytics/fbsabr.py +0 -0
  32. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/analytics/mcheston.py +0 -0
  33. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/analytics/mcsabr.py +0 -0
  34. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/analytics/mczabr.py +0 -0
  35. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/analytics/sabr.py +0 -0
  36. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/machinelearning/datasets.py +0 -0
  37. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/maths/interpolations.py +0 -0
  38. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/maths/rand.py +0 -0
  39. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/montecarlo/smoothers.py +0 -0
  40. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/projects/aad/aad_mc.py +0 -0
  41. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/projects/aad/aad_mc_nd.py +0 -0
  42. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/projects/datafiles.py +0 -0
  43. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/projects/stovol/stovolgen.py +0 -0
  44. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/projects/stovol/stovolplot.py +0 -0
  45. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/projects/stovol/stovoltrain.py +0 -0
  46. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/tools/clipboard.py +0 -0
  47. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/tools/filemanager.py +0 -0
  48. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/tools/jsonmanager.py +0 -0
  49. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/tools/timegrids.py +0 -0
  50. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/tools/timer.py +0 -0
  51. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/volsurfacegen/fbsabrgenerator.py +0 -0
  52. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/volsurfacegen/mchestongenerator.py +0 -0
  53. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/volsurfacegen/mcsabrgenerator.py +0 -0
  54. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/volsurfacegen/mczabrgenerator.py +0 -0
  55. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy/volsurfacegen/stovolfactory.py +0 -0
  56. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy.egg-info/dependency_links.txt +0 -0
  57. {sdevpy-1.0.1 → sdevpy-1.0.3}/src/sdevpy.egg-info/top_level.txt +0 -0
  58. {sdevpy-1.0.1 → sdevpy-1.0.3}/tests/test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sdevpy
3
- Version: 1.0.1
3
+ Version: 1.0.3
4
4
  Summary: Python package for Machine Learning in Finance
5
5
  Author-email: Sebastien Gurrieri <sebgur@gmail.com>
6
6
  Project-URL: Git page, https://github.com/sebgur/SDev.Python
@@ -18,6 +18,7 @@ Requires-Dist: numpy
18
18
  Requires-Dist: tensorflow
19
19
  Requires-Dist: scikit-learn
20
20
  Requires-Dist: tensorflow_probability
21
+ Requires-Dist: silence_tensorflow
21
22
 
22
23
  # SDev.Python
23
24
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sdevpy"
7
- version = "1.0.1"
7
+ version = "1.0.3"
8
8
  authors = [
9
9
  { name="Sebastien Gurrieri", email="sebgur@gmail.com" },
10
10
  ]
@@ -18,7 +18,7 @@ classifiers = [
18
18
  ]
19
19
  dependencies = [
20
20
  "pandas","pyperclip","py_vollib","numpy","tensorflow",
21
- "scikit-learn", "tensorflow_probability"
21
+ "scikit-learn", "tensorflow_probability", "silence_tensorflow"
22
22
  ]
23
23
 
24
24
  [project.urls]
@@ -0,0 +1 @@
1
+ __version__ = '1.0.3'
@@ -8,7 +8,7 @@ from sklearn.preprocessing import MinMaxScaler
8
8
  class SDevPyCallback(keras.callbacks.Callback):
9
9
  """ SDevPy's base class for callbacks. Compared to Keras's base, it also keeps track of
10
10
  the learning rate and displays it at each period """
11
- def __init__(self, epoch_sampling=1, optimizer=None):
11
+ def __init__(self, epoch_sampling=1, optimizer=None, loss_function=None, x_train=None, y_train=None):
12
12
  keras.callbacks.Callback.__init__(self)
13
13
  self.x_scaler = MinMaxScaler()
14
14
  self.y_scaler = MinMaxScaler()
@@ -22,6 +22,12 @@ class SDevPyCallback(keras.callbacks.Callback):
22
22
  self.losses = []
23
23
  self.learning_rates = []
24
24
  self.sampled_epochs = []
25
+ # Used to calculate the loss periodically, as in reality the loss function
26
+ # is used on the transformed coordinates, not, as the user might expect,
27
+ # on the real world ones.
28
+ self.loss_function = loss_function
29
+ self.x_train = x_train
30
+ self.y_train = y_train
25
31
 
26
32
  def on_train_begin(self, logs=None):
27
33
  """ Display settings, reset training variables """
@@ -46,10 +52,15 @@ class SDevPyCallback(keras.callbacks.Callback):
46
52
 
47
53
  def on_epoch_end(self, epoch, logs=None):
48
54
  self.epochs.append(epoch)
55
+ # if self.x_train is None and self.y_train is None:
49
56
  loss = logs['loss']
50
- scale = self.y_scaler.scale_[0]
51
- loss = loss * scale
57
+ # scale = self.y_scaler.scale_[0] # This is a trick only working in 1D
58
+ # loss = loss * scale
59
+ # else:
60
+ # loss = self.estimate_loss(self.x_train, self.y_train)
61
+
52
62
  self.losses.append(loss)
63
+
53
64
  if self.optimizer is not None:
54
65
  lr = self.optimizer.learning_rate.numpy()
55
66
  self.learning_rates.append(lr)
@@ -67,6 +78,17 @@ class SDevPyCallback(keras.callbacks.Callback):
67
78
  self.x_scaler = x_scaler
68
79
  self.y_scaler = y_scaler
69
80
 
81
+ def estimate_loss(self, x_est, y_est):
82
+ """ Estimate loss on test set """
83
+ x_scaled = self.x_scaler.transform(x_est)
84
+ y_scaled = self.model.predict(x_scaled, verbose=0)
85
+ # y_pred = self.y_scaler.inverse_transform(y_scaled)
86
+ y_pred = y_scaled
87
+ y_est_sc = self.y_scaler.transform(y_est)
88
+ est_loss = self.loss_function(y_est_sc, y_pred)
89
+ # est_loss = rmse(y_est, y_pred) * 10000
90
+ return est_loss
91
+
70
92
  # def convergence(self):
71
93
  # """ Retrieve sampled epochs, losses and learning rates """
72
94
  # return self.epochs, self.losses, self.learning_rates, self.sampled_epochs
@@ -75,11 +97,11 @@ class SDevPyCallback(keras.callbacks.Callback):
75
97
  class RefCallback(SDevPyCallback):
76
98
  """ Callback class that compares model predictions vs reference periodically """
77
99
  def __init__(self, x_test, y_test, loss_function, epoch_sampling=1,
78
- optimizer=None):
79
- SDevPyCallback.__init__(self, epoch_sampling, optimizer)
100
+ optimizer=None, x_train=None, y_train=None):
101
+ SDevPyCallback.__init__(self, epoch_sampling, optimizer, loss_function, x_train, y_train)
80
102
  self.x_test = x_test
81
103
  self.y_test = y_test
82
- self.loss_function = loss_function
104
+ # self.loss_function = loss_function
83
105
  self.test_losses = []
84
106
 
85
107
  def on_train_begin(self, logs=None):
@@ -98,14 +120,14 @@ class RefCallback(SDevPyCallback):
98
120
  self.test_losses.append(test_loss)
99
121
  print(f", Test loss: {test_loss:,.2f}", end="")
100
122
 
101
- def estimate_loss(self, x_est, y_est):
102
- """ Estimate loss on test set """
103
- x_scaled = self.x_scaler.transform(x_est)
104
- y_scaled = self.model.predict(x_scaled, verbose=0)
105
- y_pred = self.y_scaler.inverse_transform(y_scaled)
106
- est_loss = self.loss_function(y_est, y_pred)
107
- # est_loss = rmse(y_est, y_pred) * 10000
108
- return est_loss
123
+ # def estimate_loss(self, x_est, y_est):
124
+ # """ Estimate loss on test set """
125
+ # x_scaled = self.x_scaler.transform(x_est)
126
+ # y_scaled = self.model.predict(x_scaled, verbose=0)
127
+ # y_pred = self.y_scaler.inverse_transform(y_scaled)
128
+ # est_loss = self.loss_function(y_est, y_pred)
129
+ # # est_loss = rmse(y_est, y_pred) * 10000
130
+ # return est_loss
109
131
 
110
132
  def test_set_loss(self):
111
133
  """ Retrieve loss on test set """
@@ -7,6 +7,7 @@ import tensorflow as tf
7
7
  import joblib
8
8
  import absl.logging
9
9
  from sdevpy.tools import jsonmanager
10
+ from sdevpy.tools import filemanager
10
11
 
11
12
  class LearningModel:
12
13
  """ Wrapper class for machine learning models, including scalers, and simplifying
@@ -54,10 +55,12 @@ class LearningModel:
54
55
 
55
56
  def save(self, path):
56
57
  """ Save model and its scalers to files """
58
+ filemanager.check_directory(path)
57
59
  # Save keras model first. Turn dummy warning off temporarily.
58
60
  verbosity = absl.logging.get_verbosity()
59
61
  absl.logging.set_verbosity(absl.logging.ERROR)
60
- self.model.save(path)
62
+ model_file = os.path.join(path, "model.keras")
63
+ self.model.save(model_file)
61
64
  absl.logging.set_verbosity(verbosity)
62
65
 
63
66
  # Save scalers
@@ -132,7 +135,8 @@ def load_learning_model(path, compile_=False):
132
135
  if os.path.exists(path) is False:
133
136
  raise RuntimeError("Model folder does not exist: " + path)
134
137
 
135
- keras_model = tf.keras.models.load_model(path, compile=compile_)
138
+ model_file = os.path.join(path, "model.keras")
139
+ keras_model = tf.keras.models.load_model(model_file, compile=compile_)
136
140
 
137
141
  x_scaler_file, y_scaler_file = scaler_files(path)
138
142
  if os.path.exists(x_scaler_file) and os.path.exists(y_scaler_file):
@@ -0,0 +1,78 @@
1
+ """ Custom learning schedules """
2
+ import tensorflow as tf
3
+ import math
4
+ from sdevpy.tools.constants import TWO_PI
5
+
6
+ # Custom learning rate scheduler, exponentially decreases between given values
7
+ class FlooredExponentialDecay(tf.keras.optimizers.schedules.LearningRateSchedule):
8
+ """ Custom learning rate scheduler, exponentially decreases between given values """
9
+ def __init__(self, num_samples, batch_size, target_epoch, initial_lr=1e-1, final_lr=1e-4):
10
+ self.initial_lr = initial_lr
11
+ self.final_lr = final_lr
12
+ # self.decay = decay
13
+ # self.decay_steps = decay_steps
14
+ # A step is the usage of one gradient, i.e. for one batch. As we go through the whole sample
15
+ # in 1 epoch, the number of steps per epoch is given by the number of batches per epoch
16
+ # i.e. the formula below.
17
+ steps_per_epoch = num_samples / batch_size
18
+ percent_reached = 0.10 # Percentage of the final LR reached by the chosen epoch
19
+ self.decay = final_lr * percent_reached / (initial_lr - final_lr)
20
+ self.steps_to_target = np.float32(steps_per_epoch * target_epoch)
21
+
22
+ def __call__(self, step):
23
+ ratio = tf.cast(step / self.steps_to_target, tf.float32)
24
+ coeff = tf.pow(self.decay, ratio)
25
+ ampl = self.initial_lr - self.final_lr
26
+ return self.final_lr + ampl * coeff
27
+
28
+ # def __call__(self, step):
29
+ # ratio = tf.cast(step / self.decay_steps, tf.float32)
30
+ # coeff = tf.pow(self.decay, ratio)
31
+ # return self.initial_lr * coeff + self.final_lr * (1.0 - coeff)
32
+
33
+ def get_config(self):
34
+ config = { 'initial_lr': self.initial_lr,
35
+ 'final_lr': self.final_lr,
36
+ 'decay': self.decay,
37
+ 'decay_steps': self.steps_to_target }
38
+ return config
39
+
40
+ import numpy as np
41
+
42
+ # Custom learning rate scheduler, cyclically exponentially decreases between given values
43
+ class CyclicalExponentialDecay(tf.keras.optimizers.schedules.LearningRateSchedule):
44
+ """ Custom learning rate scheduler, cyclically exponentially decreases between given values """
45
+ def __init__(self, num_samples, batch_size, target_epoch, initial_lr=1e-1, final_lr=1e-4,
46
+ periods=10.0):
47
+ # Amplitude decay
48
+ self.initial_lr = initial_lr
49
+ self.final_lr = final_lr
50
+ # self.period = periods
51
+ # A step is the usage of one gradient, i.e. for one batch. As we go through the whole sample
52
+ # in 1 epoch, the number of steps per epoch is given by the number of batches per epoch
53
+ # i.e. the formula below.
54
+ steps_per_epoch = num_samples / batch_size
55
+ percent_reached = 0.10 # Percentage of the final LR reached by the chosen epoch
56
+ self.decay = final_lr * percent_reached / (initial_lr - final_lr)
57
+ self.steps_to_target = np.float32(steps_per_epoch * target_epoch)
58
+
59
+ # Oscillations
60
+ self.steps_per_period = np.float32(target_epoch * steps_per_epoch / periods)
61
+
62
+ def __call__(self, step):
63
+ ratio = tf.cast(step / self.steps_to_target, tf.float32)
64
+ coeff = tf.pow(self.decay, ratio)
65
+ ampl = self.initial_lr - self.final_lr
66
+ two_pi = tf.cast(TWO_PI, tf.float32)
67
+ arg = tf.cast(step / self.steps_per_period, tf.float32)
68
+ oscillation = (2.0 + tf.math.cos(arg * two_pi)) / 2.0 # Between 0.5 and 1.5
69
+ ampl = ampl * oscillation
70
+ return self.final_lr + ampl * coeff
71
+
72
+ def get_config(self):
73
+ config = { 'initial_lr': self.initial_lr,
74
+ 'final_lr': self.final_lr,
75
+ 'decay': self.decay,
76
+ 'steps_to_target': self.steps_to_target,
77
+ 'steps_per_period': self.steps_per_period }
78
+ return config
@@ -20,7 +20,7 @@ def compose_model(num_inputs, num_outputs, hidden_layers, neurons, dropout=0.2):
20
20
  model = tf.keras.Sequential()
21
21
 
22
22
  # Input layer
23
- model.add(tf.keras.Input(num_inputs))
23
+ model.add(tf.keras.Input(shape=(num_inputs,)))
24
24
 
25
25
  # Hidden layers
26
26
  for layer in hidden_layers:
@@ -3,6 +3,13 @@ import numpy as np
3
3
  from sklearn.metrics import mean_squared_error
4
4
  import tensorflow as tf
5
5
 
6
+ def mse(set1, set2):
7
+ """ Mean Squared Error """
8
+ return mean_squared_error(set1, set2)
9
+
10
+ def rmsew(set1, set2, weights):
11
+ """ Root Mean Squared Error with weights """
12
+ return np.sqrt(mean_squared_error(set1, set2, sample_weight=weights))
6
13
 
7
14
  def rmse(set1, set2):
8
15
  """ Root Mean Squared Error """
@@ -13,6 +20,10 @@ def bps_rmse(y_true, y_ref):
13
20
  return 10000.0 * rmse(y_true, y_ref)
14
21
 
15
22
  # Tensorflow versions
23
+ def tf_mse(y_true, y_pred):
24
+ """ Mean Squared Error in tensorflow """
25
+ return tf.math.reduce_mean(tf.square(y_true - y_pred))
26
+
16
27
  def tf_rmse(y_true, y_pred):
17
28
  """ Root Mean Squared Error in tensorflow """
18
29
  return tf.sqrt(tf.math.reduce_mean(tf.square(y_true - y_pred)))
@@ -0,0 +1,158 @@
1
+ """ Optimization """
2
+ # https://machinelearningmastery.com/how-to-use-nelder-mead-optimization-in-python/
3
+ # https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html
4
+ # For DE: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.differential_evolution.html
5
+
6
+ from abc import abstractmethod
7
+ import numpy as np
8
+ import scipy.optimize as opt
9
+ import matplotlib.pyplot as plt
10
+
11
+
12
+ SCIPY_OPTIMIZERS = ['Nelder-Mead', 'Powell', 'CG', 'BFGS', 'L-BFGS-B', 'TNC', 'COBYLA',
13
+ 'SLSQP', 'trust-constr', 'Newton-CG', 'dogleg', 'trust-ncg',
14
+ 'trust-exact', 'trust-krylov', 'DE']
15
+
16
+
17
+ # Available strategies for DE
18
+ # best1bin, best1exp, best2exp, best2bin, rand1bin, rand1exp, rand2bin, rand2exp,
19
+ # randtobest1bin, randtobest1exp, currenttobest1bin, currenttobest1exp
20
+
21
+ def create_optimizer(method, **kwargs):
22
+ """ Create an optimizer. Currently only supporting SciPy """
23
+ optimizer = None
24
+ if method in SCIPY_OPTIMIZERS:
25
+ optimizer = SciPyOptimizer(method, **kwargs)
26
+ else:
27
+ raise RuntimeError("Optimizer type not supported: " + method)
28
+
29
+ return optimizer
30
+
31
+ def create_bounds(lw_bounds, up_bounds):
32
+ return opt.Bounds(lw_bounds, up_bounds, keep_feasible=False)
33
+
34
+
35
+ class Optimizer:
36
+ # def __init__(self):
37
+
38
+ @abstractmethod
39
+ def minimize(self, f, x0, args, bounds):
40
+ """ Minimization """
41
+
42
+
43
+ class SciPyOptimizer(Optimizer):
44
+ """ Wrapper for SciPy optimizers, including differential_evolution """
45
+ def __init__(self, method = 'Powell', **kwargs):
46
+ self.method_ = method
47
+ self.std_minimizers = ['Nelder-Mead', 'Powell', 'CG', 'BFGS', 'L-BFGS-B', 'TNC', 'COBYLA',
48
+ 'SLSQP', 'trust-constr', 'Newton-CG', 'dogleg', 'trust-ncg',
49
+ 'trust-exact', 'trust-krylov']
50
+
51
+ self.other_minimizers = ['DE']
52
+
53
+ self.kwargs = kwargs
54
+
55
+ if self.method_ not in self.std_minimizers and self.method_ not in self.other_minimizers:
56
+ raise RuntimeError("Method " + self.method_ + " not found in SciPy")
57
+
58
+ def minimize(self, f, x0=None, args=(), bounds=None):
59
+ result = None
60
+ if self.method_ in self.std_minimizers:
61
+ tol = self.kwargs.get('tol', None)
62
+ result = opt.minimize(f, x0, args, method=self.method_, bounds=bounds,
63
+ tol=tol)
64
+ elif self.method_== 'DE':
65
+ atol = self.kwargs.get('atol', 0)
66
+ popsize = self.kwargs.get('popsize', 15)
67
+ strategy = self.kwargs.get('strategy', 'best1bin')
68
+ recombination = self.kwargs.get('recombination', 0.7)
69
+ mutation = self.kwargs.get('mutation', (0.5, 1.0))
70
+ result = opt.differential_evolution(f, x0=x0, args=args, bounds=bounds, atol=atol,
71
+ popsize=popsize, strategy=strategy,
72
+ recombination=recombination)
73
+ else:
74
+ raise RuntimeError("Method " + self.method_ + " not recognized")
75
+
76
+ return result
77
+
78
+ class MultiOptimizer(Optimizer):
79
+ """ Wrapper for SciPy optimizers, including differential_evolution """
80
+ def __init__(self, methods = ['L-BFGS-B', 'DE'], mtol=1e-4, **kwargs):
81
+ self.methods_ = methods
82
+ self.mtol_ = mtol
83
+ self.kwargs = kwargs
84
+
85
+ self.optimizers_ = []
86
+ for method in self.methods_:
87
+ self.optimizers_.append(create_optimizer(method, **kwargs))
88
+
89
+ def minimize(self, f, x0=None, args=(), bounds=None):
90
+ result = None
91
+ for i, optimizer in enumerate(self.optimizers_):
92
+ print("Trying optimization using " + self.methods_[i] + ": ", end='')
93
+ result = optimizer.minimize(f, x0, args, bounds)
94
+ if result.fun < self.mtol_:
95
+ print("SUCCESS!")
96
+ break
97
+ else:
98
+ print("FAILURE")
99
+
100
+ return result
101
+
102
+
103
+ if __name__ == "__main__":
104
+ # Objective function
105
+ def f(x, *args):
106
+ x_ = x[0]
107
+ a = args[0]
108
+ b = args[1]
109
+ c = args[2]
110
+ prod = a * b * c
111
+ bi = a * b + b * c + a * c
112
+ sum = a + b + c
113
+ return 0.25 * np.power(x_, 4) - sum / 3.0 * np.power(x_, 3) + 0.5 * bi * x_**2 - prod * x_ + 1
114
+
115
+
116
+ # Choose method
117
+ method = 'Nelder-Mead'
118
+ # method = "Powell" # Success x^4
119
+ # method = "CG"
120
+ # method = "BFGS"
121
+ # method = "L-BFGS-B"
122
+ # method = "TNC"
123
+ # method = "COBYLA" # Success x^4
124
+ # method = "SLSQP"
125
+ # method = "trust-constr"
126
+ # method = "Newton-CG" # Requires Jacobian
127
+ # method = "dogleg" # Requires Jacobian
128
+ # method = "trust-ncg" # Requires Jacobian
129
+ # method = "trust-exact" # Requires Jacobian
130
+ # method = "trust-krylov" # Requires Jacobian
131
+ # method = "DE" # Success x^4
132
+
133
+ # Create the optimizer
134
+ optimizer = create_optimizer(method)
135
+
136
+ # Define the bounds
137
+ bounds = opt.Bounds([0], [4], keep_feasible=False)
138
+
139
+ # Optimize
140
+ result = optimizer.minimize(f, x0=[1.5], args=(1, 2, 3.2), bounds=bounds)
141
+
142
+ for key in result.keys():
143
+ if key in result:
144
+ print(key + "\n", result[key])
145
+
146
+ x = result.x
147
+ fun = result.fun
148
+ # print("Keys\n", result.keys())
149
+
150
+ # Plot
151
+ points = np.linspace(0, 4, 100).reshape(-1, 1)
152
+ y = []
153
+ for p in points:
154
+ y.append(f(p, 1, 2, 3.2))
155
+
156
+ plt.plot(points, y, color='blue', alpha=0.8, label='Objective')
157
+ plt.scatter(x[0], fun, color='red', alpha=1.0, label='Solution')
158
+ plt.show()
@@ -0,0 +1,75 @@
1
+ """ Generate training data for Stochastic Local Vol models. We implement the inverse map here.
2
+ Datasets of parameters (outputs) vs prices/implied vols (inputs) are generated to later train
3
+ a network that learns the so-called 'inverse' calculation, i.e. parameters from prices. """
4
+ import os
5
+ from datetime import datetime
6
+ from sdevpy.volsurfacegen import stovolfactory
7
+ from sdevpy import settings
8
+ from sdevpy.tools import filemanager
9
+ from sdevpy.tools.timer import Stopwatch
10
+
11
+ # ################ Runtime configuration ##########################################################
12
+ MODEL_TYPE = "SABR"
13
+ # MODEL_TYPE = "McSABR"
14
+ # MODEL_TYPE = "FbSABR"
15
+ # MODEL_TYPE = "McZABR"
16
+ # MODEL_TYPE = "McHeston"
17
+ SHIFT = 0.03
18
+ NUM_SAMPLES = 1000 * 1000
19
+ # The 4 parameters below are only relevant for models whose reference is calculated by MC
20
+ NUM_EXPIRIES = 15
21
+ NUM_MC = 100 * 1000 # 100 * 1000
22
+ POINTS_PER_YEAR = 25 # 25
23
+ SEED = 1234 # [1357, 8642, 1000, 8888, 4444, 2222, 1111, 4321, 1234, 42]
24
+ SPREADS = [-200, -100, -75, -50, -25, -10, 0, 10, 25, 50, 75, 100, 200]
25
+ USE_NVOL = True
26
+
27
+ print(">> Set up runtime configuration")
28
+ project_folder = os.path.join(settings.WORKFOLDER, "stovolinv")
29
+ print("> Project folder: " + project_folder)
30
+ dataset_folder = os.path.join(project_folder, "datasets")
31
+ print("> Data folder: " + dataset_folder)
32
+ filemanager.check_directory(dataset_folder)
33
+ print("> Chosen model: " + MODEL_TYPE)
34
+
35
+ # ################ Select model ###################################################################
36
+ generator = stovolfactory.set_generator(MODEL_TYPE, SHIFT, NUM_EXPIRIES, num_mc=NUM_MC,
37
+ points_per_year=POINTS_PER_YEAR, seed=SEED)
38
+
39
+ # ################ Select training ranges #########################################################
40
+ # SABR
41
+ RANGES = {'Ttm': [1.0 / 12.0, 35.0], 'F': [-0.009, 0.041], 'LnVol': [0.05, 0.5],
42
+ 'Beta': [0.1, 0.9], 'Nu': [0.1, 1.0], 'Rho': [-0.6, 0.6]}
43
+ # # FBSABR
44
+ # RANGES = {'Ttm': [1.0 / 12.0, 5.0], 'F': [-0.009, 0.041], 'LnVol': [0.05, 0.5],
45
+ # 'Beta': [0.25, 0.75], 'Nu': [0.1, 1.0], 'Rho': [-0.6, 0.6]}
46
+ # # ZABR
47
+ # RANGES = {'Ttm': [1.0 / 12.0, 35.0], 'F': [-0.009, 0.041], 'LnVol': [0.05, 0.5],
48
+ # 'Beta': [0.1, 0.9], 'Nu': [0.10, 1.0], 'Rho': [-0.6, 0.6],
49
+ # 'Gamma': [0.1, 0.9]}
50
+ # Heston
51
+ # RANGES = {'Ttm': [1.0 / 12.0, 35.0], 'F': [-0.009, 0.041], 'LnVol': [0.05, 0.25],
52
+ # 'Kappa': [0.25, 4.00], 'Theta': [0.05**2, 0.25**2], 'Xi': [0.10, 0.50],
53
+ # 'Rho': [-0.40, 0.40]}
54
+
55
+ # ################ Generate dataset ###############################################################
56
+ print(">> Generate dataset")
57
+
58
+ print(f"> Generate {NUM_SAMPLES:,} price samples")
59
+ timer_gen = Stopwatch("Generating Samples")
60
+ timer_gen.trigger()
61
+ data_df = generator.generate_samples_inverse(NUM_SAMPLES, RANGES, SPREADS, USE_NVOL)
62
+ timer_gen.stop()
63
+
64
+ timer_out = Stopwatch("File Output")
65
+ timer_out.trigger()
66
+ now = datetime.now()
67
+ dt_string = now.strftime("%Y%m%d-%H_%M_%S")
68
+ data_file = os.path.join(dataset_folder, MODEL_TYPE + "_data_" + dt_string + ".tsv")
69
+ print("> Output to file: " + data_file)
70
+ generator.to_file(data_df, data_file)
71
+ timer_out.stop()
72
+
73
+ # Show timers
74
+ timer_gen.print()
75
+ timer_out.print()