qablet-basic 0.2.2__cp310-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 +0 -0
- qablet/_qablet.cp310-win_amd64.pyd +0 -0
- qablet/base/base.py +47 -0
- qablet/base/fixed.py +13 -0
- qablet/base/flags.py +7 -0
- qablet/base/mc.py +33 -0
- qablet/base/tests/test_schemas.py +48 -0
- qablet/base/utils.py +56 -0
- qablet/black_scholes/__init__.py +0 -0
- qablet/black_scholes/fd.py +16 -0
- qablet/heston/mc.py +131 -0
- qablet/hullwhite/fd.py +16 -0
- qablet/local_vol/mc.py +16 -0
- qablet_basic-0.2.2.dist-info/METADATA +5 -0
- qablet_basic-0.2.2.dist-info/RECORD +16 -0
- qablet_basic-0.2.2.dist-info/WHEEL +4 -0
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
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,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=S0rB2hDy1vo9ABZi7A01rQ8AAoGmft-elqksCH-s-Bw,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.cp310-win_amd64.pyd,sha256=_qLyti5jZ-M7jRHqcAPbztpQzaQZ6OhxeJrWBgajJAM,1267200
|
|
16
|
+
qablet_basic-0.2.2.dist-info/RECORD,,
|