moqua 0.2.2__tar.gz → 0.2.4__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 (29) hide show
  1. {moqua-0.2.2 → moqua-0.2.4}/PKG-INFO +1 -1
  2. {moqua-0.2.2 → moqua-0.2.4}/moqua/__init__.py +4 -1
  3. moqua-0.2.4/moqua/pricing/engine/__init__.py +1 -0
  4. moqua-0.2.4/moqua/pricing/engine/bachelier.py +83 -0
  5. moqua-0.2.4/moqua/pricing/engine/black76.py +95 -0
  6. moqua-0.2.4/moqua/pricing/engine/black_scholes.py +100 -0
  7. moqua-0.2.4/moqua/pricing/instruments/__init__.py +1 -0
  8. moqua-0.2.4/moqua/pricing/instruments/european_option.py +37 -0
  9. {moqua-0.2.2 → moqua-0.2.4}/moqua.egg-info/PKG-INFO +1 -1
  10. {moqua-0.2.2 → moqua-0.2.4}/moqua.egg-info/SOURCES.txt +6 -0
  11. {moqua-0.2.2 → moqua-0.2.4}/setup.py +1 -1
  12. {moqua-0.2.2 → moqua-0.2.4}/README.md +0 -0
  13. {moqua-0.2.2 → moqua-0.2.4}/moqua/core.py +0 -0
  14. {moqua-0.2.2 → moqua-0.2.4}/moqua/optimization.py +0 -0
  15. {moqua-0.2.2 → moqua-0.2.4}/moqua/outputs.py +0 -0
  16. {moqua-0.2.2 → moqua-0.2.4}/moqua/pricing/__init__.py +0 -0
  17. {moqua-0.2.2 → moqua-0.2.4}/moqua/pricing/contracts.py +0 -0
  18. {moqua-0.2.2 → moqua-0.2.4}/moqua.egg-info/dependency_links.txt +0 -0
  19. {moqua-0.2.2 → moqua-0.2.4}/moqua.egg-info/not-zip-safe +0 -0
  20. {moqua-0.2.2 → moqua-0.2.4}/moqua.egg-info/requires.txt +0 -0
  21. {moqua-0.2.2 → moqua-0.2.4}/moqua.egg-info/top_level.txt +0 -0
  22. {moqua-0.2.2 → moqua-0.2.4}/pyproject.toml +0 -0
  23. {moqua-0.2.2 → moqua-0.2.4}/setup.cfg +0 -0
  24. {moqua-0.2.2 → moqua-0.2.4}/src/erf_cody.cpp +0 -0
  25. {moqua-0.2.2 → moqua-0.2.4}/src/lets_be_rational.cpp +0 -0
  26. {moqua-0.2.2 → moqua-0.2.4}/src/normaldistribution.cpp +0 -0
  27. {moqua-0.2.2 → moqua-0.2.4}/src/optimization.cpp +0 -0
  28. {moqua-0.2.2 → moqua-0.2.4}/src/rationalcubic.cpp +0 -0
  29. {moqua-0.2.2 → moqua-0.2.4}/src/rolling_ols.cpp +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: moqua
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Quantitative Finance Tools
5
5
  Author: M. Drici
6
6
  Requires-Dist: numpy
@@ -20,6 +20,9 @@ from .core import (
20
20
  log_returns
21
21
  )
22
22
 
23
+ # Explicitly expose subpackages for better discovery
24
+ from . import contracts, engine, instruments
25
+
23
26
  # ==========================================
24
27
  # 2. PRICING (Moteurs & Instruments)
25
28
  # ==========================================
@@ -65,4 +68,4 @@ from .outputs import (
65
68
  create_excel_report
66
69
  )
67
70
 
68
- __version__ = "0.2.0"
71
+ __version__ = "0.2.4"
@@ -0,0 +1 @@
1
+ # Init file for engine module
@@ -0,0 +1,83 @@
1
+ import numpy as np
2
+ from moqua.core import pdf, cdf
3
+ from moqua.pricing.contracts import Calculator, Engine, Option, Market
4
+ from functools import cached_property
5
+
6
+ class BachelierCalculator(Calculator):
7
+
8
+ def __init__(self, F, K, T, r, sigma_N, w):
9
+ self.F = np.asarray(F, float)
10
+ self.K = np.asarray(K, float)
11
+ self.T = np.asarray(T, float)
12
+ self.r = np.asarray(r, float)
13
+ self.sigma_N = np.asarray(sigma_N, float)
14
+ self.w = np.asarray(w, float)
15
+
16
+ @cached_property
17
+ def discount(self):
18
+ return np.exp(-self.r * self.T)
19
+
20
+ @cached_property
21
+ def mask(self):
22
+ return (self.T <= 0.0) | (self.sigma_N <= 1e-10)
23
+
24
+ @cached_property
25
+ def safe_sqrt_T(self):
26
+ return np.where(~self.mask, np.sqrt(self.T), 1.0)
27
+
28
+ @cached_property
29
+ def d(self):
30
+ return (self.F - self.K) / (self.sigma_N * self.safe_sqrt_T)
31
+
32
+ @cached_property
33
+ def price(self):
34
+ val = self.discount * (
35
+ self.w * (self.F - self.K) * cdf(self.w * self.d) +
36
+ self.sigma_N * np.sqrt(np.maximum(self.T, 0.0)) * pdf(self.d)
37
+ )
38
+ payoff = np.maximum(0.0, self.w * (self.F - self.K))
39
+ return self._result(np.where(self.mask, payoff, val))
40
+
41
+ @cached_property
42
+ def delta(self):
43
+ val = self.w * self.discount * cdf(self.w * self.d)
44
+ payoff = np.where(self.w * (self.F - self.K) > 0, self.w * self.discount, 0.0)
45
+ return self._result(np.where(self.mask, payoff, val))
46
+
47
+ @cached_property
48
+ def gamma(self):
49
+ with np.errstate(divide='ignore', invalid='ignore'):
50
+ val = self.discount * pdf(self.d) / (self.sigma_N * self.safe_sqrt_T)
51
+ return self._result(np.where(self.mask, 0.0, val))
52
+
53
+ @cached_property
54
+ def vega(self):
55
+ val = self.discount * self.safe_sqrt_T * pdf(self.d)
56
+ return self._result(np.where(self.mask, 0.0, val))
57
+
58
+ @cached_property
59
+ def rho(self):
60
+ val = -self.T * self.price
61
+ return self._result(np.where(self.T <= 0, 0.0, val))
62
+
63
+ @cached_property
64
+ def theta(self):
65
+ with np.errstate(divide='ignore', invalid='ignore'):
66
+ term1 = -(self.discount * self.sigma_N * pdf(self.d)) / (2.0 * np.sqrt(np.maximum(self.T, 1e-10)))
67
+
68
+ term2 = self.r * self.price
69
+ val = (term1 + term2) / 365.0
70
+ return self._result(np.where(self.T <= 0, 0.0, val))
71
+
72
+
73
+ class BachelierEngine(Engine):
74
+ def calculator(self, option: Option, market: Market):
75
+ if market.F is None and market.S is None:
76
+ raise ValueError("Bachelier requires 'forward' or 'spot'.")
77
+
78
+ F = market.F if market.F is not None else market.S * np.exp(market.r * option.T)
79
+
80
+ return BachelierCalculator(
81
+ F=F, K=option.K, # No np.maximum(F, 1e-10) because Bachelier handles negative F and K!
82
+ T=option.T, r=market.r, sigma_N=market.sigma, w=option.w
83
+ )
@@ -0,0 +1,95 @@
1
+ import numpy as np
2
+ from moqua.core import pdf, cdf
3
+ from moqua.pricing.contracts import Calculator, Engine, Option, Market
4
+ from functools import cached_property
5
+
6
+ class Black76Calculator(Calculator):
7
+
8
+ def __init__(self, F, K, T, r, sigma, w):
9
+ self.F = np.asarray(F, float)
10
+ self.K = np.asarray(K, float)
11
+ self.T = np.asarray(T, float)
12
+ self.r = np.asarray(r, float)
13
+ self.sigma = np.asarray(sigma, float)
14
+ self.w = np.asarray(w, float)
15
+
16
+ @cached_property
17
+ def discount(self):
18
+ return np.exp(-self.r * self.T)
19
+
20
+ @cached_property
21
+ def mask(self):
22
+ return (self.T <= 0.0) | (self.sigma <= 1e-10)
23
+
24
+ @cached_property
25
+ def vol_sqrt_T(self):
26
+ return self.sigma * np.sqrt(np.maximum(self.T, 0.0))
27
+
28
+ @cached_property
29
+ def safe_vol(self):
30
+ return np.where(~self.mask, self.vol_sqrt_T, 1.0)
31
+
32
+ @cached_property
33
+ def d1(self):
34
+ with np.errstate(divide='ignore', invalid='ignore'):
35
+ return (np.log(self.F / self.K) + (0.5 * self.sigma ** 2) * self.T) / self.safe_vol
36
+
37
+ @cached_property
38
+ def d2(self):
39
+ return self.d1 - self.vol_sqrt_T
40
+
41
+ @cached_property
42
+ def price(self):
43
+ val = self.w * self.discount * (self.F * cdf(self.w * self.d1) - self.K * cdf(self.w * self.d2))
44
+ payoff = np.maximum(0.0, self.w * (self.F - self.K))
45
+
46
+ return self._result(np.where(self.mask, payoff, val))
47
+
48
+ @cached_property
49
+ def delta(self):
50
+
51
+ val = self.w * self.discount * cdf(self.w * self.d1)
52
+ payoff = np.where(self.w * (self.F - self.K) > 0, self.w * self.discount, 0.0)
53
+
54
+ return self._result(np.where(self.mask, payoff, val))
55
+
56
+ @cached_property
57
+ def gamma(self):
58
+
59
+ with np.errstate(divide='ignore', invalid='ignore'):
60
+ val = self.discount * pdf(self.d1) / (self.F * self.vol_sqrt_T)
61
+ bad = self.mask | (self.F <= 0)
62
+
63
+ return self._result(np.where(bad, 0.0, val))
64
+
65
+ @cached_property
66
+ def vega(self):
67
+ val = self.F * self.discount * pdf(self.d1) * np.sqrt(np.maximum(self.T, 0.0))
68
+ return self._result(np.where(self.mask, 0.0, val))
69
+
70
+ @cached_property
71
+ def rho(self):
72
+ val = -self.T * self.price
73
+ return self._result(np.where(self.T <= 0, 0.0, val))
74
+
75
+ @cached_property
76
+ def theta(self):
77
+ with np.errstate(divide='ignore', invalid='ignore'):
78
+ term1 = -(self.F * self.discount * pdf(self.d1) * self.sigma) / (2.0 * np.sqrt(np.maximum(self.T, 1e-10)))
79
+
80
+ term2 = self.r * self.price
81
+ val = (term1 + term2) / 365.0
82
+ return self._result(np.where(self.T <= 0, 0.0, val))
83
+
84
+
85
+ class Black76Engine(Engine):
86
+ def calculator(self, option: Option, market: Market):
87
+ if market.F is None and market.S is None:
88
+ raise ValueError("Black76 requires 'forward' or 'spot'.")
89
+
90
+ F = market.F if market.F is not None else market.S * np.exp(market.r * option.T)
91
+
92
+ return Black76Calculator(
93
+ F=np.maximum(F, 1e-10), K=option.K,
94
+ T=option.T, r=market.r, sigma=market.sigma, w=option.w
95
+ )
@@ -0,0 +1,100 @@
1
+ import numpy as np
2
+ from moqua.core import pdf, cdf
3
+ from moqua.pricing.contracts import Calculator, Engine, Option, Market
4
+ from functools import cached_property
5
+
6
+ class BlackScholesCalculator(Calculator):
7
+
8
+ def __init__(self, S, K, T, r, sigma, w):
9
+ self.S = np.asarray(S, float)
10
+ self.K = np.asarray(K, float)
11
+ self.T = np.asarray(T, float)
12
+ self.r = np.asarray(r, float)
13
+ self.sigma = np.asarray(sigma, float)
14
+ self.w = np.asarray(w, float)
15
+
16
+ # Private helpers
17
+ @cached_property
18
+ def discount(self):
19
+ return np.exp(-self.r * self.T)
20
+
21
+ @cached_property
22
+ def mask(self):
23
+ return (self.T <= 0) | (self.sigma <= 1e-10)
24
+
25
+ @cached_property
26
+ def vol_sqrt_T(self):
27
+ return self.sigma * np.sqrt(np.maximum(self.T, 0.0))
28
+
29
+ @cached_property
30
+ def safe_vol(self):
31
+ return np.where(~self.mask, self.vol_sqrt_T, 1.0)
32
+
33
+ @cached_property
34
+ def d1(self):
35
+ with np.errstate(divide='ignore', invalid='ignore'):
36
+ return (np.log(self.S / self.K) + (self.r + 0.5 * self.sigma**2) * self.T) / self.safe_vol
37
+
38
+ @cached_property
39
+ def d2(self):
40
+ return self.d1 - self.vol_sqrt_T
41
+
42
+ @cached_property
43
+ def price(self):
44
+
45
+ val = self.w * (self.S * cdf(self.w * self.d1) - self.K * self.discount * cdf(self.w * self.d2))
46
+ payoff = np.maximum(0.0, self.w * (self.S - self.K))
47
+
48
+ return self._result(np.where(self.mask, payoff, val))
49
+
50
+ @cached_property
51
+ def delta(self):
52
+
53
+ val = self.w * cdf(self.w * self.d1)
54
+ payoff = np.where(self.w * (self.S - self.K) > 0, self.w, 0.0) # ITM -> w, OTM -> 0
55
+
56
+ return self._result(np.where(self.mask, payoff, val))
57
+
58
+ @cached_property
59
+ def gamma(self):
60
+
61
+ with np.errstate(divide='ignore', invalid='ignore'):
62
+ val = pdf(self.d1) / (self.S * self.vol_sqrt_T)
63
+ bad = self.mask | (self.S <= 0)
64
+
65
+ return self._result(np.where(bad, 0.0, val))
66
+
67
+ @cached_property
68
+ def vega(self):
69
+
70
+ val = self.S * pdf(self.d1) * np.sqrt(np.maximum(self.T, 0.0))
71
+
72
+ return self._result(np.where(self.mask, 0.0, val))
73
+
74
+ @cached_property
75
+ def rho(self):
76
+
77
+ val = self.w * self.K * self.T * self.discount * cdf(self.w * self.d2)
78
+
79
+ return self._result(np.where(self.T <= 0, 0.0, val))
80
+
81
+ @cached_property
82
+ def theta(self):
83
+
84
+ with np.errstate(divide='ignore', invalid='ignore'):
85
+ term1 = -(self.S * pdf(self.d1) * self.sigma) / (2.0 * np.sqrt(np.maximum(self.T, 1e-10)))
86
+
87
+ term2 = -self.w * self.r * self.K * self.discount * cdf(self.w * self.d2)
88
+ val = (term1 + term2) / 365.0
89
+
90
+ return self._result(np.where(self.T <= 0, 0.0, val))
91
+
92
+
93
+ class BlackScholesEngine(Engine):
94
+ def calculator(self, option: Option, market: Market):
95
+ if market.S is None: raise ValueError("Spot is missing")
96
+ return BlackScholesCalculator(
97
+ S=np.maximum(market.S, 1e-10), K=option.K,
98
+ T=option.T, r=market.r, sigma=market.sigma, w=option.w
99
+ )
100
+
@@ -0,0 +1 @@
1
+ # Init file for instruments module
@@ -0,0 +1,37 @@
1
+ import numpy as np
2
+ from moqua.pricing.contracts import Calculator, Engine, Option, Market
3
+
4
+ class EuropeanOptionPricer:
5
+
6
+ def __init__(self, engine: Engine, yield_curve=None, vol_surface=None):
7
+ self.engine = engine
8
+ self.yield_curve = yield_curve
9
+ self.vol_surface = vol_surface
10
+
11
+ def set_engine(self, new_engine: Engine):
12
+ self.engine = new_engine
13
+
14
+ def get_calculator(self, K, T, spot=None, forward=None, r=None, sigma=None, option_type='call') -> Calculator:
15
+ w = np.where(np.asarray(option_type) == 'call', 1.0, -1.0)
16
+ option = Option(K=K, T=T, w=w)
17
+
18
+ if r is not None:
19
+ r_val = r
20
+ elif self.yield_curve is not None:
21
+ r_val = self.yield_curve.get_rate(T)
22
+ else:
23
+ raise ValueError("You must provide 'r' directly or configure a 'yield_curve'.")
24
+
25
+ if sigma is not None:
26
+ sigma_val = sigma
27
+ elif self.vol_surface is not None:
28
+ sigma_val = self.vol_surface.get_vol(K, T)
29
+ else:
30
+ raise ValueError("You must provide 'sigma' directly or configure a 'vol_surface'.")
31
+
32
+ market = Market(
33
+ r=r_val,
34
+ sigma=sigma_val,
35
+ S=spot, F=forward
36
+ )
37
+ return self.engine.calculator(option, market)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: moqua
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Quantitative Finance Tools
5
5
  Author: M. Drici
6
6
  Requires-Dist: numpy
@@ -13,6 +13,12 @@ moqua.egg-info/requires.txt
13
13
  moqua.egg-info/top_level.txt
14
14
  moqua/pricing/__init__.py
15
15
  moqua/pricing/contracts.py
16
+ moqua/pricing/engine/__init__.py
17
+ moqua/pricing/engine/bachelier.py
18
+ moqua/pricing/engine/black76.py
19
+ moqua/pricing/engine/black_scholes.py
20
+ moqua/pricing/instruments/__init__.py
21
+ moqua/pricing/instruments/european_option.py
16
22
  src/erf_cody.cpp
17
23
  src/lets_be_rational.cpp
18
24
  src/normaldistribution.cpp
@@ -26,7 +26,7 @@ ext_modules = [
26
26
 
27
27
  setup(
28
28
  name="moqua",
29
- version="0.2.2",
29
+ version="0.2.4",
30
30
  packages=find_packages(),
31
31
  ext_modules=ext_modules,
32
32
  install_requires=[
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes