qablet-basic 0.2.2__cp312-none-win_amd64.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.
qablet/__init__.py ADDED
File without changes
Binary file
qablet/base/base.py ADDED
@@ -0,0 +1,47 @@
1
+ # Define Base Class for Models
2
+ from abc import ABC, abstractmethod
3
+
4
+
5
+ # Define Base Class for State Object for all Models
6
+ class ModelStateBase(ABC):
7
+ """Class to maintain the state during a model execution."""
8
+
9
+ def __init__(self, timetable, dataset):
10
+ self.stats = {}
11
+
12
+ def set_stat(self, key: str, val):
13
+ self.stats[key] = val
14
+
15
+
16
+ class Model(ABC):
17
+ """Base class for all models."""
18
+
19
+ @abstractmethod
20
+ def state_class(self):
21
+ """The class that maintains state for this model."""
22
+ ...
23
+
24
+ @abstractmethod
25
+ def price_method(self):
26
+ """The method that calculates price."""
27
+ ...
28
+
29
+ def price(self, timetable, dataset):
30
+ """Calculate price of contract.
31
+
32
+ Parameters:
33
+ timetable (dict): timetable for the contract.
34
+ dataset (dict): dataset for the model.
35
+
36
+ Returns:
37
+ price (float): price of contract
38
+ stats (dict): stats such as standard error
39
+
40
+ """
41
+
42
+ model_state = (self.state_class())(timetable, dataset)
43
+ price = self.price_method()(
44
+ timetable["events"], model_state, dataset, timetable["expressions"]
45
+ )
46
+
47
+ return price, model_state.stats
qablet/base/fixed.py ADDED
@@ -0,0 +1,13 @@
1
+ # Define the fixed model
2
+
3
+ from .._qablet import fixed_price
4
+ from .base import Model, ModelStateBase
5
+
6
+
7
+ # Define a determinitic Model that just uses forwards
8
+ class FixedModel(Model):
9
+ def state_class(self):
10
+ return ModelStateBase
11
+
12
+ def price_method(self):
13
+ return fixed_price
qablet/base/flags.py ADDED
@@ -0,0 +1,7 @@
1
+ from enum import IntFlag
2
+
3
+
4
+ class Stats(IntFlag):
5
+ PV_VEC = 1
6
+ CHOICES = 2
7
+ PV_GRID = 4
qablet/base/mc.py ADDED
@@ -0,0 +1,33 @@
1
+ # Generic MC model
2
+
3
+ from .base import Model, ModelStateBase
4
+ from abc import abstractmethod
5
+
6
+ from .._qablet import mc_price
7
+
8
+
9
+ # Define Base Class for State Object for MC Models
10
+ # Todo add the abstract methods and what else is expected from this class.
11
+ class MCStateBase(ModelStateBase):
12
+ """Class to maintain the state of a single asset MC process."""
13
+
14
+ def get_value(self, unit):
15
+ """Return the value of the asset at the current time,
16
+ if this asset is handled by the model, otherwise return None."""
17
+ return None
18
+
19
+ @abstractmethod
20
+ def advance(self, new_time: float):
21
+ ...
22
+
23
+ def set_stat(self, key: str, val):
24
+ self.stats[key] = val
25
+
26
+
27
+ # Define Base Class for MC Models
28
+ class MCModel(Model):
29
+ """Abstract base class for all Monte Carlo models where the stochastic model
30
+ is implemented in the python class."""
31
+
32
+ def price_method(self):
33
+ return mc_price
@@ -0,0 +1,48 @@
1
+ # Description: Tests for the Heston model for vanilla options.
2
+
3
+ import unittest
4
+ import numpy as np
5
+
6
+
7
+ from qablet.base.utils import Discounter
8
+
9
+
10
+ class TestDiscountSchema(unittest.TestCase):
11
+ """Tests for the discount schemas."""
12
+
13
+ def test_discounts(self):
14
+ """Test the Discounter class."""
15
+
16
+ # define a discount curve using the zero rate schema
17
+ times = np.array([0.0, 1.0, 2.0, 5.0])
18
+ zero_rates = np.array([0.04, 0.04, 0.045, 0.05])
19
+
20
+ test_times = [0.1, 1.0, 3.0, 5.0]
21
+ expected_logdf = [
22
+ 0.1 * 0.04,
23
+ 1.0 * 0.04,
24
+ 2.0 * 0.045 + (0.05 * 5 - 0.045 * 2) * (3 - 2) / (5 - 2),
25
+ 5.0 * 0.05,
26
+ ]
27
+
28
+ # define a discount curve using the zero rate schema
29
+ zero_data = ("ZERO_RATES", np.column_stack((times, zero_rates)))
30
+
31
+ # define a discount curve using the log discount schema
32
+ log_dfs = -times * zero_rates
33
+ logdf_data = ("LOG_DISCOUNTS", np.column_stack((times, log_dfs)))
34
+
35
+ for df_data in [zero_data, logdf_data]:
36
+ discounter = Discounter(df_data)
37
+ schema_name = df_data[0]
38
+ print(f"Testing {schema_name} schema")
39
+ with self.subTest(schema_name=schema_name):
40
+ for time, logdf in zip(test_times, expected_logdf):
41
+ df = discounter.discount(time)
42
+ expected_df = np.exp(-logdf)
43
+ self.assertAlmostEqual(df, expected_df, places=6)
44
+ print(f"{df:11.6f} {expected_df:11.6f} {df - expected_df:9.6f}")
45
+
46
+
47
+ if __name__ == "__main__":
48
+ unittest.main()
qablet/base/utils.py ADDED
@@ -0,0 +1,56 @@
1
+ # Utility classes and functions for models.
2
+
3
+ from scipy import interpolate
4
+ import numpy as np
5
+
6
+
7
+ # Define a class for discount factors and rates.
8
+ class Discounter:
9
+ """A class for discount factors and rates."""
10
+
11
+ def __init__(self, discount_data):
12
+ data_type, data = discount_data
13
+ if data_type == "LOG_DISCOUNTS":
14
+ # the columns are times and log discounts
15
+ times = data[:, 0]
16
+ log_discounts = data[:, 1]
17
+ elif data_type == "ZERO_RATES":
18
+ # the columns are times and zero rates
19
+ times = data[:, 0]
20
+ zero_rates = data[:, 1]
21
+ log_discounts = -zero_rates * times
22
+
23
+ self.log_discount_fn = interpolate.interp1d(times, log_discounts)
24
+
25
+ def rate(self, end, start=0):
26
+ ld_end, ld_start = self.log_discount_fn([end, start])
27
+
28
+ return (ld_start - ld_end) / (end - start)
29
+
30
+ def discount(self, t):
31
+ return np.exp(self.log_discount_fn(t))
32
+
33
+
34
+ # Define a class for forwards of an asset.
35
+ class Forwards:
36
+ """A class for forwards and forward rates."""
37
+
38
+ def __init__(self, forwards_data):
39
+ _, data = forwards_data
40
+
41
+ times = data[:, 0]
42
+ fwds = data[:, 1]
43
+
44
+ self.log_forward_fn = interpolate.interp1d(times, np.log(fwds))
45
+
46
+ def rate(self, end, start=0):
47
+ ld_end, ld_start = self.log_forward_fn([end, start])
48
+ return (ld_end - ld_start) / (end - start)
49
+
50
+ def forward(self, t):
51
+ return np.exp(self.log_forward_fn(t))
52
+
53
+
54
+ def discounter_from_dataset(dataset):
55
+ """Return a discounter from a dataset."""
56
+ return Discounter(dataset["ASSETS"][dataset["BASE"]])
File without changes
@@ -0,0 +1,16 @@
1
+ # Black Scholes model using finite difference method
2
+
3
+
4
+ from qablet.base.base import Model, ModelStateBase
5
+ from .._qablet import fd_blackscholes_price
6
+
7
+
8
+ # Define the Model Class
9
+ class BSFDModel(Model):
10
+ __PARAM_SCHEMA_NAME__ = "BS"
11
+
12
+ def state_class(self):
13
+ return ModelStateBase
14
+
15
+ def price_method(self):
16
+ return fd_blackscholes_price
qablet/heston/mc.py ADDED
@@ -0,0 +1,131 @@
1
+ # Monte Carlo Pricer for Heston Model
2
+
3
+ import numpy as np
4
+ from math import sqrt
5
+ from qablet.base.mc import MCStateBase, MCModel
6
+ from numpy.random import Generator, SFC64
7
+ from qablet.base.utils import Forwards
8
+
9
+
10
+ # Define a class for the state of a single asset Heston MC process
11
+ class HestonStateMC(MCStateBase):
12
+ def __init__(self, timetable, dataset):
13
+ super().__init__(timetable, dataset)
14
+
15
+ self.shape = dataset["MC"]["PATHS"]
16
+ assert self.shape % 2 == 0, "Number of paths must be even"
17
+ self.n = self.shape >> 1 # divide by 2
18
+
19
+ # create a random number generator
20
+ self.rng = Generator(SFC64(dataset["MC"]["SEED"]))
21
+
22
+ self.asset = dataset["HESTON"]["ASSET"]
23
+ self.asset_fwd = Forwards(dataset["ASSETS"][self.asset])
24
+ self.spot = self.asset_fwd.forward(0)
25
+
26
+ self.heston_params = (
27
+ dataset["HESTON"]["LONG_VAR"],
28
+ dataset["HESTON"]["VOL_OF_VAR"],
29
+ dataset["HESTON"]["MEANREV"],
30
+ dataset["HESTON"]["CORRELATION"],
31
+ )
32
+
33
+ # Initialize the arrays
34
+ self.x_vec = np.zeros(self.shape) # processes x (log stock)
35
+ self.v_vec = np.full(
36
+ self.shape, dataset["HESTON"]["INITIAL_VAR"]
37
+ ) # processes v (variance)
38
+
39
+ # We will reduce time spent in memory allocation by creating arrays in advance
40
+ # and reusing them in the `advance` function which is called repeatedly.
41
+ # though the values from one timestep are not reused in the next.
42
+ self.tmp_vec = np.empty(self.shape, dtype=np.float64)
43
+ self.dz1_vec = np.empty(self.shape, dtype=np.float64)
44
+ self.dz2_vec = np.empty(self.shape, dtype=np.float64)
45
+ self.vol_vec = np.empty(self.shape, dtype=np.float64)
46
+ self.sv_vec = np.empty(self.shape, dtype=np.float64)
47
+ self.cur_time = 0
48
+
49
+ def advance(self, new_time):
50
+ """Update x_vec, v_vec in place when we move simulation by time dt."""
51
+ dt = new_time - self.cur_time
52
+ if dt < 1e-10:
53
+ return
54
+
55
+ (theta, vol_of_variance, mean_reversion_speed, correlation) = self.heston_params
56
+ fwd_rate = self.asset_fwd.rate(new_time, self.cur_time)
57
+
58
+ sqrtdt = sqrt(dt)
59
+ n = self.n
60
+
61
+ # To improve preformance we will break up the operations into np.multiply,
62
+ # np.add, etc. and use the `out` parameter to avoid creating temporary arrays.
63
+
64
+ # generate the random numbers
65
+ # we calculate dz1 = normal(0,1) * sqrtdt
66
+ self.rng.standard_normal(
67
+ n, out=self.dz1_vec[0:n]
68
+ ) # not much difference using out= or not
69
+ np.multiply(sqrtdt, self.dz1_vec[0:n], out=self.dz1_vec[0:n])
70
+ np.negative(self.dz1_vec[0:n], out=self.dz1_vec[n:]) # antithetic variates
71
+
72
+ # we calculate dz2 = normal(0,1) * sqrtdt * sqrt(1 - correlation * correlation) + correlation * dz1
73
+ self.rng.standard_normal(n, out=self.dz2_vec[0:n])
74
+ np.multiply(
75
+ sqrtdt * sqrt(1 - correlation * correlation),
76
+ self.dz2_vec[0:n],
77
+ out=self.dz2_vec[0:n],
78
+ )
79
+ np.negative(self.dz2_vec[0:n], out=self.dz2_vec[n:]) # antithetic variates
80
+ np.multiply(correlation, self.dz1_vec, out=self.tmp_vec) # second term
81
+ np.add(self.dz2_vec, self.tmp_vec, out=self.dz2_vec)
82
+
83
+ # vol = sqrt(max(v, 0))
84
+ np.maximum(0.0, self.v_vec, out=self.vol_vec)
85
+ np.sqrt(self.vol_vec, out=self.vol_vec)
86
+
87
+ # update the current value of x (log Stock process)
88
+ # first term: x += (fwd_rate - vol * vol / 2.) * dt
89
+ np.multiply(self.vol_vec, self.vol_vec, out=self.tmp_vec)
90
+ np.divide(self.tmp_vec, 2, out=self.tmp_vec)
91
+ np.subtract(fwd_rate, self.tmp_vec, out=self.tmp_vec)
92
+ np.multiply(self.tmp_vec, dt, out=self.tmp_vec)
93
+ np.add(self.x_vec, self.tmp_vec, out=self.x_vec)
94
+
95
+ # second term: x += vol * dz1
96
+ np.multiply(self.vol_vec, self.dz1_vec, out=self.tmp_vec)
97
+ np.add(self.x_vec, self.tmp_vec, out=self.x_vec)
98
+
99
+ # update the current value of v (variance process)
100
+ # first term: v += mean_reversion_speed * (theta - v) * dt
101
+ np.subtract(theta, self.v_vec, out=self.tmp_vec)
102
+ np.multiply(self.tmp_vec, (mean_reversion_speed * dt), out=self.tmp_vec)
103
+ np.add(self.v_vec, self.tmp_vec, out=self.v_vec)
104
+
105
+ # second term: v += vol_of_variance * vol * dz2
106
+ np.multiply(vol_of_variance, self.vol_vec, out=self.tmp_vec)
107
+ np.multiply(self.tmp_vec, self.dz2_vec, out=self.tmp_vec)
108
+ np.add(self.v_vec, self.tmp_vec, out=self.v_vec)
109
+
110
+ # Millstein correction
111
+ # third term: v += 0.25 * vol_of_variance * vol_of_variance * (dz2 ** 2 - dt)
112
+ np.multiply(self.dz2_vec, self.dz2_vec, out=self.tmp_vec)
113
+ np.subtract(self.tmp_vec, dt, out=self.tmp_vec)
114
+ np.multiply(
115
+ 0.25 * vol_of_variance * vol_of_variance, self.tmp_vec, out=self.tmp_vec
116
+ )
117
+ np.add(self.v_vec, self.tmp_vec, out=self.v_vec)
118
+
119
+ self.cur_time = new_time
120
+
121
+ def get_value(self, unit):
122
+ """Return the value of the unit at the current time."""
123
+ if unit == self.asset:
124
+ return self.spot * np.exp(self.x_vec)
125
+ else:
126
+ return None
127
+
128
+
129
+ class HestonMCModel(MCModel):
130
+ def state_class(self):
131
+ return HestonStateMC
qablet/hullwhite/fd.py ADDED
@@ -0,0 +1,16 @@
1
+ # Hullwhite model using finite difference method
2
+
3
+
4
+ from qablet.base.base import Model, ModelStateBase
5
+ from .._qablet import fd_hullwhite_price
6
+
7
+
8
+ # Define the Model Class
9
+ class HWFDModel(Model):
10
+ __PARAM_SCHEMA_NAME__ = "HW"
11
+
12
+ def state_class(self):
13
+ return ModelStateBase
14
+
15
+ def price_method(self):
16
+ return fd_hullwhite_price
qablet/local_vol/mc.py ADDED
@@ -0,0 +1,16 @@
1
+ # Local Vol model using Monte Carlo method
2
+
3
+
4
+ from qablet.base.base import Model, ModelStateBase
5
+ from .. import _qablet
6
+
7
+
8
+ # Define the Model Class
9
+ class LVMCModel(Model):
10
+ __PARAM_SCHEMA_NAME__ = "LV"
11
+
12
+ def state_class(self):
13
+ return ModelStateBase
14
+
15
+ def price_method(self):
16
+ return _qablet.mc_lv_price
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.1
2
+ Name: qablet_basic
3
+ Version: 0.2.2
4
+ Summary: A suite of basic models to price qablet contracts. See qablet-contracts for details.
5
+ Project-URL: Documentation, https://qablet-academy.github.io/intro/
@@ -0,0 +1,16 @@
1
+ qablet_basic-0.2.2.dist-info/METADATA,sha256=ErmXKJ0HNva75X7nOqJc-LIPcEzq0NaPm9MiiqKvI_4,218
2
+ qablet_basic-0.2.2.dist-info/WHEEL,sha256=yJI21RgSFtgqtm9j1AeUXX045H75HDPQyXNVFThVOh8,95
3
+ qablet/base/base.py,sha256=7C3eLnCa41QZJ_q2SNSAcAr-5Q_sKbHFTkWAoCM-Vh0,1260
4
+ qablet/base/fixed.py,sha256=whP8JoNtAMFvWigKJsQ0suhSr9pwvBk3JI9g9vx_E6Q,307
5
+ qablet/base/flags.py,sha256=ES4RZzlsvm-_H2ajxLqh4OKnYNp1vbMfWTZB4a0A6hg,103
6
+ qablet/base/mc.py,sha256=3KK_I11tqE2-793GC5XRrkTpIWXkbZWL6EF30hdmuVA,952
7
+ qablet/base/tests/test_schemas.py,sha256=LqUMZLRBI2Cq-iEHybEkHpIxkNpBdgPrBTUsq-nMoLg,1661
8
+ qablet/base/utils.py,sha256=Yly7mGHxjeqkVyhgP0fhs8tZr0rLymH2BgzcXDVIxxc,1693
9
+ qablet/black_scholes/fd.py,sha256=Rca9LBvQOBwgvM919NrMP0L6kITu51tu_TxPAx1XNNM,374
10
+ qablet/black_scholes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ qablet/heston/mc.py,sha256=W7blKSNlTVLjrnq-bXBirUafn6JIt6q51dlhGJqpsCU,5486
12
+ qablet/hullwhite/fd.py,sha256=plH3uygS37DSWfA6MwDEgkrQ38dVDKvVgmoLovEiH8A,364
13
+ qablet/local_vol/mc.py,sha256=2c4pVvcZdMjPhwqWf8eLcdKcmS3vTJaphg0i8bQbWzc,341
14
+ qablet/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ qablet/_qablet.cp312-win_amd64.pyd,sha256=qC6qzqCu6gVngEJf8bPuHiZaI0i6hpWy-RdoeH_z-2U,1262080
16
+ qablet_basic-0.2.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.4.0)
3
+ Root-Is-Purelib: false
4
+ Tag: cp312-none-win_amd64