finmat 0.1.0__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.
finmat-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,98 @@
1
+ Metadata-Version: 2.4
2
+ Name: finmat
3
+ Version: 0.1.0
4
+ Summary: A financial math library for options pricing, stochastic processes, and fixed income analytics
5
+ Author-email: Daniel Butler <danielbutler245@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/dbu79/fin-mat-library
8
+ Requires-Python: >=3.8
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: numpy
11
+ Requires-Dist: scipy
12
+ Requires-Dist: matplotlib
13
+
14
+ # Financial Math Library
15
+
16
+ A Python library for pricing derivatives, simulating stochastic processes, and analyzing fixed income instruments.
17
+
18
+ ## Features
19
+
20
+ - **Options Pricing**
21
+ - Black-Scholes analytical pricing
22
+ - Heston stochastic volatility model
23
+ - Monte Carlo simulation
24
+ - Binomial/trinomial trees
25
+ - Implied volatility calculation
26
+
27
+ - **Stochastic Processes**
28
+ - Arithmetic Brownian Motion (ABM)
29
+ - Geometric Brownian Motion (GBM)
30
+ - Cox-Ingersoll-Ross (CIR)
31
+ - Ornstein-Uhlenbeck (OU)
32
+ - Vasicek
33
+ - Jump diffusion (Merton)
34
+
35
+ - **Fixed Income**
36
+ - Bond pricing and yield curve tools
37
+
38
+ - **Portfolio**
39
+ - Risk and performance metrics
40
+
41
+ ## Project Structure
42
+
43
+ ```
44
+ fixed_income/
45
+ fixed_income.py # Fixed income pricing and analytics
46
+
47
+ options/
48
+ iv.py # Implied volatility solver
49
+ option.py # Option contract definitions
50
+
51
+ portfolio/
52
+ metrics.py # Portfolio risk/performance metrics
53
+
54
+ pricing/
55
+ black_scholes.py # Black-Scholes model
56
+ heston.py # Heston stochastic volatility model
57
+ monte_carlo.py # Monte Carlo pricing engine
58
+ trees.py # Binomial/trinomial tree pricing
59
+
60
+ processes/
61
+ abm.py # Arithmetic Brownian Motion
62
+ cir.py # Cox-Ingersoll-Ross process
63
+ gbm.py # Geometric Brownian Motion
64
+ jump_diffusion.py # Jump diffusion process
65
+ ou.py # Ornstein-Uhlenbeck process
66
+ vasicek.py # Vasicek interest rate model
67
+
68
+ testing.ipynb # Example usage and validation notebook
69
+ ```
70
+
71
+ ## Installation
72
+
73
+ ```bash
74
+ git clone <repo-url>
75
+ cd <repo-name>
76
+ pip install -r requirements.txt
77
+ ```
78
+
79
+ ## Usage
80
+
81
+ See `testing.ipynb` for example workflows, including:
82
+
83
+ - Pricing European options with Black-Scholes and comparing to Monte Carlo
84
+ - Simulating asset paths with GBM, CIR, and jump diffusion processes
85
+ - Calibrating the Heston model to market data
86
+ - Computing implied volatility surfaces
87
+ - Evaluating fixed income instruments and portfolio metrics
88
+
89
+ ## Requirements
90
+
91
+ - Python 3.8+
92
+ - NumPy
93
+ - SciPy
94
+ - matplotlib (for notebook visualizations)
95
+
96
+ ## License
97
+
98
+ This project is licensed under the MIT License - see the LICENSE file for details.
finmat-0.1.0/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # Financial Math Library
2
+
3
+ A Python library for pricing derivatives, simulating stochastic processes, and analyzing fixed income instruments.
4
+
5
+ ## Features
6
+
7
+ - **Options Pricing**
8
+ - Black-Scholes analytical pricing
9
+ - Heston stochastic volatility model
10
+ - Monte Carlo simulation
11
+ - Binomial/trinomial trees
12
+ - Implied volatility calculation
13
+
14
+ - **Stochastic Processes**
15
+ - Arithmetic Brownian Motion (ABM)
16
+ - Geometric Brownian Motion (GBM)
17
+ - Cox-Ingersoll-Ross (CIR)
18
+ - Ornstein-Uhlenbeck (OU)
19
+ - Vasicek
20
+ - Jump diffusion (Merton)
21
+
22
+ - **Fixed Income**
23
+ - Bond pricing and yield curve tools
24
+
25
+ - **Portfolio**
26
+ - Risk and performance metrics
27
+
28
+ ## Project Structure
29
+
30
+ ```
31
+ fixed_income/
32
+ fixed_income.py # Fixed income pricing and analytics
33
+
34
+ options/
35
+ iv.py # Implied volatility solver
36
+ option.py # Option contract definitions
37
+
38
+ portfolio/
39
+ metrics.py # Portfolio risk/performance metrics
40
+
41
+ pricing/
42
+ black_scholes.py # Black-Scholes model
43
+ heston.py # Heston stochastic volatility model
44
+ monte_carlo.py # Monte Carlo pricing engine
45
+ trees.py # Binomial/trinomial tree pricing
46
+
47
+ processes/
48
+ abm.py # Arithmetic Brownian Motion
49
+ cir.py # Cox-Ingersoll-Ross process
50
+ gbm.py # Geometric Brownian Motion
51
+ jump_diffusion.py # Jump diffusion process
52
+ ou.py # Ornstein-Uhlenbeck process
53
+ vasicek.py # Vasicek interest rate model
54
+
55
+ testing.ipynb # Example usage and validation notebook
56
+ ```
57
+
58
+ ## Installation
59
+
60
+ ```bash
61
+ git clone <repo-url>
62
+ cd <repo-name>
63
+ pip install -r requirements.txt
64
+ ```
65
+
66
+ ## Usage
67
+
68
+ See `testing.ipynb` for example workflows, including:
69
+
70
+ - Pricing European options with Black-Scholes and comparing to Monte Carlo
71
+ - Simulating asset paths with GBM, CIR, and jump diffusion processes
72
+ - Calibrating the Heston model to market data
73
+ - Computing implied volatility surfaces
74
+ - Evaluating fixed income instruments and portfolio metrics
75
+
76
+ ## Requirements
77
+
78
+ - Python 3.8+
79
+ - NumPy
80
+ - SciPy
81
+ - matplotlib (for notebook visualizations)
82
+
83
+ ## License
84
+
85
+ This project is licensed under the MIT License - see the LICENSE file for details.
@@ -0,0 +1,98 @@
1
+ Metadata-Version: 2.4
2
+ Name: finmat
3
+ Version: 0.1.0
4
+ Summary: A financial math library for options pricing, stochastic processes, and fixed income analytics
5
+ Author-email: Daniel Butler <danielbutler245@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/dbu79/fin-mat-library
8
+ Requires-Python: >=3.8
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: numpy
11
+ Requires-Dist: scipy
12
+ Requires-Dist: matplotlib
13
+
14
+ # Financial Math Library
15
+
16
+ A Python library for pricing derivatives, simulating stochastic processes, and analyzing fixed income instruments.
17
+
18
+ ## Features
19
+
20
+ - **Options Pricing**
21
+ - Black-Scholes analytical pricing
22
+ - Heston stochastic volatility model
23
+ - Monte Carlo simulation
24
+ - Binomial/trinomial trees
25
+ - Implied volatility calculation
26
+
27
+ - **Stochastic Processes**
28
+ - Arithmetic Brownian Motion (ABM)
29
+ - Geometric Brownian Motion (GBM)
30
+ - Cox-Ingersoll-Ross (CIR)
31
+ - Ornstein-Uhlenbeck (OU)
32
+ - Vasicek
33
+ - Jump diffusion (Merton)
34
+
35
+ - **Fixed Income**
36
+ - Bond pricing and yield curve tools
37
+
38
+ - **Portfolio**
39
+ - Risk and performance metrics
40
+
41
+ ## Project Structure
42
+
43
+ ```
44
+ fixed_income/
45
+ fixed_income.py # Fixed income pricing and analytics
46
+
47
+ options/
48
+ iv.py # Implied volatility solver
49
+ option.py # Option contract definitions
50
+
51
+ portfolio/
52
+ metrics.py # Portfolio risk/performance metrics
53
+
54
+ pricing/
55
+ black_scholes.py # Black-Scholes model
56
+ heston.py # Heston stochastic volatility model
57
+ monte_carlo.py # Monte Carlo pricing engine
58
+ trees.py # Binomial/trinomial tree pricing
59
+
60
+ processes/
61
+ abm.py # Arithmetic Brownian Motion
62
+ cir.py # Cox-Ingersoll-Ross process
63
+ gbm.py # Geometric Brownian Motion
64
+ jump_diffusion.py # Jump diffusion process
65
+ ou.py # Ornstein-Uhlenbeck process
66
+ vasicek.py # Vasicek interest rate model
67
+
68
+ testing.ipynb # Example usage and validation notebook
69
+ ```
70
+
71
+ ## Installation
72
+
73
+ ```bash
74
+ git clone <repo-url>
75
+ cd <repo-name>
76
+ pip install -r requirements.txt
77
+ ```
78
+
79
+ ## Usage
80
+
81
+ See `testing.ipynb` for example workflows, including:
82
+
83
+ - Pricing European options with Black-Scholes and comparing to Monte Carlo
84
+ - Simulating asset paths with GBM, CIR, and jump diffusion processes
85
+ - Calibrating the Heston model to market data
86
+ - Computing implied volatility surfaces
87
+ - Evaluating fixed income instruments and portfolio metrics
88
+
89
+ ## Requirements
90
+
91
+ - Python 3.8+
92
+ - NumPy
93
+ - SciPy
94
+ - matplotlib (for notebook visualizations)
95
+
96
+ ## License
97
+
98
+ This project is licensed under the MIT License - see the LICENSE file for details.
@@ -0,0 +1,26 @@
1
+ README.md
2
+ pyproject.toml
3
+ finmat.egg-info/PKG-INFO
4
+ finmat.egg-info/SOURCES.txt
5
+ finmat.egg-info/dependency_links.txt
6
+ finmat.egg-info/requires.txt
7
+ finmat.egg-info/top_level.txt
8
+ fixed_income/__init__.py
9
+ fixed_income/fixed_income.py
10
+ options/__init__.py
11
+ options/iv.py
12
+ options/option.py
13
+ portfolio/__init__.py
14
+ portfolio/metrics.py
15
+ pricing/__init__.py
16
+ pricing/black_scholes.py
17
+ pricing/heston.py
18
+ pricing/monte_carlo.py
19
+ pricing/trees.py
20
+ processes/__init__.py
21
+ processes/abm.py
22
+ processes/cir.py
23
+ processes/gbm.py
24
+ processes/jump_diffusion.py
25
+ processes/ou.py
26
+ processes/vasicek.py
@@ -0,0 +1,3 @@
1
+ numpy
2
+ scipy
3
+ matplotlib
@@ -0,0 +1,5 @@
1
+ fixed_income
2
+ options
3
+ portfolio
4
+ pricing
5
+ processes
@@ -0,0 +1 @@
1
+ from fixed_income import simple_interest, compound_interest, future_value, present_value, annuity_pv, annuity_fv, bond_price, convexity
@@ -0,0 +1,27 @@
1
+ def simple_interest(P: float, r: float, t: float) -> float:
2
+ return P * (1 + r * t)
3
+
4
+ def compound_interest(P: float, r: float, n: int, t: float) -> float:
5
+ return P * (1 + r/n)**(n*t)
6
+
7
+ def future_value(PV: float, r: float, n: int) -> float:
8
+ return PV * (1 + r)**n
9
+
10
+ def present_value(FV: float, r: float, n: int) -> float:
11
+ return FV / (1 + r)**n
12
+
13
+ def annuity_pv(PMT: float, r: float, n: int) -> float:
14
+ return PMT * (1 - (1 + r)**-n) / r
15
+
16
+ def annuity_fv(PMT: float, r: float, n: int) -> float:
17
+ return PMT * ((1 + r)**n - 1) / r
18
+
19
+ def bond_price(PMT: float, FV: float, r: float, n: int) -> float:
20
+ """PMT: coupon per period, FV: face value, r: yield per period, n: number of periods"""
21
+ coupons = sum(PMT / (1 + r)**t for t in range(1, n + 1))
22
+ principal = FV / (1 + r)**n
23
+ return coupons + principal
24
+
25
+ def convexity(P0: float, P_plus: float, P_minus: float, dy: float) -> float:
26
+ """P_plus/P_minus: prices after +dy/-dy yield shock"""
27
+ return (P_plus + P_minus - 2 * P0) / (P0 * dy**2)
@@ -0,0 +1,2 @@
1
+ from .iv import ImpliedVolatility
2
+ from .option import Option
@@ -0,0 +1,33 @@
1
+ import numpy as np
2
+ from copy import deepcopy
3
+ from .option import Option
4
+
5
+ class ImpliedVolatility:
6
+ @staticmethod
7
+ def solve(option: Option, market_price, pricer, initial=0.2, tol=1e-8, max_iter=100, h=1e-4, seed=None):
8
+ sigma = initial
9
+ copy_ = deepcopy(option)
10
+ def price_at(sigma_val):
11
+ copy_.sigma = sigma_val
12
+ if seed is not None:
13
+ np.random.seed(seed)
14
+ return pricer(copy_)
15
+
16
+ for _ in range(max_iter):
17
+ model_price = price_at(sigma)
18
+
19
+ if abs(model_price - market_price) < tol:
20
+ return sigma
21
+
22
+ price_up = price_at(sigma + h)
23
+ price_down = price_at(sigma - h)
24
+ vega = (price_up - price_down) / (2 * h)
25
+ if abs(vega) < 1e-10 or not np.isfinite(vega):
26
+ return np.nan
27
+
28
+ sigma_new = sigma - (model_price - market_price) / vega
29
+ if not np.isfinite(sigma_new) or sigma_new <= 0:
30
+ return np.nan
31
+ sigma = sigma_new
32
+
33
+ return np.nan
@@ -0,0 +1,23 @@
1
+ class Option:
2
+ """European option contract with Black-Scholes-style parameters."""
3
+
4
+ VALID_TYPES = ('call', 'put')
5
+
6
+ def __init__(self, S: float, K: float, T: float, r: float, sigma: float, opt_type: str = 'call'):
7
+ if S <= 0:
8
+ raise ValueError("Spot price must be greater than 0")
9
+ if K <= 0:
10
+ raise ValueError("Strike price must be greater than 0")
11
+ if T < 0:
12
+ raise ValueError("Time to expiry must be non-negative")
13
+ if sigma <= 0:
14
+ raise ValueError("Sigma must be greater than 0")
15
+ if opt_type not in self.VALID_TYPES:
16
+ raise ValueError(f"opt_type must be one of {self.VALID_TYPES}")
17
+
18
+ self.S = S
19
+ self.K = K
20
+ self.T = T
21
+ self.r = r
22
+ self.sigma = sigma
23
+ self.opt_type = opt_type
@@ -0,0 +1 @@
1
+ from metrics import PortfolioMetrics
@@ -0,0 +1,43 @@
1
+ import numpy as np
2
+
3
+
4
+ class PortfolioMetrics:
5
+ """Performance/risk metrics for a daily returns series."""
6
+
7
+ def __init__(self, returns, risk_free_rate: float = 0.0):
8
+ self.returns = np.asarray(returns, dtype=float)
9
+ self.risk_free_rate = risk_free_rate # annualized
10
+
11
+ def annualized_return(self) -> float:
12
+ return np.mean(self.returns) * 252
13
+
14
+ def annualized_volatility(self) -> float:
15
+ return np.std(self.returns, ddof=1) * np.sqrt(252)
16
+
17
+ def sharpe_ratio(self) -> float:
18
+ vol = self.annualized_volatility()
19
+ return (self.annualized_return() - self.risk_free_rate) / vol if vol != 0 else np.inf
20
+
21
+ def downside_deviation(self) -> float:
22
+ daily_rf = self.risk_free_rate / 252
23
+ downside = self.returns[self.returns < daily_rf]
24
+ if len(downside) == 0:
25
+ return 0.0
26
+ return np.sqrt(np.mean((downside - daily_rf)**2)) * np.sqrt(252)
27
+
28
+ def sortino_ratio(self) -> float:
29
+ downside_vol = self.downside_deviation()
30
+ if downside_vol == 0:
31
+ return np.inf
32
+ return (self.annualized_return() - self.risk_free_rate) / downside_vol
33
+
34
+ def max_drawdown(self) -> float:
35
+ """Assumes self.returns is chronologically ordered."""
36
+ cumulative_returns = np.cumprod(1 + self.returns)
37
+ running_max = np.maximum.accumulate(cumulative_returns)
38
+ drawdowns = (cumulative_returns - running_max) / running_max
39
+ return np.min(drawdowns)
40
+
41
+ def calmar_ratio(self) -> float:
42
+ max_dd = self.max_drawdown()
43
+ return self.annualized_return() / abs(max_dd) if max_dd < 0 else np.inf
@@ -0,0 +1,4 @@
1
+ from .monte_carlo import MonteCarloPricer
2
+ from .black_scholes import BlackScholesPricer
3
+ from .trees import BinomialTreePricer
4
+ from .heston import HestonPricer
@@ -0,0 +1,107 @@
1
+ from options.option import Option
2
+ import numpy as np
3
+ from scipy.stats import norm
4
+
5
+ class BlackScholesPricer:
6
+ """Black-Scholes pricing and Greeks for European Options."""
7
+ @staticmethod
8
+ def price(option: Option):
9
+ S = option.S
10
+ K = option.K
11
+ T = option.T
12
+ r = option.r
13
+ sigma = option.sigma
14
+ opt_type = option.opt_type
15
+
16
+ d1 = (np.log(S / K) + (r + (sigma**2)/2)*T)/(sigma*np.sqrt(T))
17
+ d2 = d1 - sigma*np.sqrt(T)
18
+
19
+ if opt_type == 'call':
20
+ return S * norm.cdf(d1) - K * np.exp(-r*T) * norm.cdf(d2)
21
+ elif opt_type == 'put':
22
+ return K * np.exp(-r*T) * norm.cdf(-d2) - S * norm.cdf(-d1)
23
+ else:
24
+ raise ValueError("Valid types are: 'call' and 'put'")
25
+
26
+ @staticmethod
27
+ def delta(option: Option) -> float:
28
+ S = option.S
29
+ K = option.K
30
+ T = option.T
31
+ r = option.r
32
+ sigma = option.sigma
33
+ opt_type = option.opt_type
34
+
35
+ d1 = (np.log(S / K) + (r + (sigma**2)/2)*T)/(sigma*np.sqrt(T))
36
+
37
+ if opt_type == 'call':
38
+ return norm.cdf(d1)
39
+ elif opt_type == 'put':
40
+ return norm.cdf(d1) - 1
41
+ else:
42
+ raise ValueError("Valid types are: 'call' and 'put'")
43
+
44
+ @staticmethod
45
+ def gamma(option: Option) -> float:
46
+ S = option.S
47
+ K = option.K
48
+ T = option.T
49
+ r = option.r
50
+ sigma = option.sigma
51
+
52
+ d1 = (np.log(S / K) + (r + (sigma**2)/2)*T)/(sigma*np.sqrt(T))
53
+ return norm.pdf(d1)/(S * sigma * np.sqrt(T))
54
+
55
+ @staticmethod
56
+ def vega(option: Option) -> float:
57
+ """Per 1% change in volatility"""
58
+ S = option.S
59
+ K = option.K
60
+ T = option.T
61
+ r = option.r
62
+ sigma = option.sigma
63
+
64
+ d1 = (np.log(S / K) + (r + (sigma**2)/2)*T)/(sigma*np.sqrt(T))
65
+ return S * norm.pdf(d1) * np.sqrt(T) / 100
66
+
67
+ @staticmethod
68
+ def theta(option: Option) -> float:
69
+ """Per day"""
70
+ S = option.S
71
+ K = option.K
72
+ T = option.T
73
+ r = option.r
74
+ sigma = option.sigma
75
+ opt_type = option.opt_type
76
+
77
+ d1 = (np.log(S / K) + (r + (sigma**2)/2)*T)/(sigma*np.sqrt(T))
78
+ d2 = d1 - sigma*np.sqrt(T)
79
+
80
+ base_theta = -(S * norm.pdf(d1) * sigma) / (2 * np.sqrt(T))
81
+ if opt_type == 'call':
82
+ return (base_theta - (r * K * np.exp(-r * T) * norm.cdf(d2)))/365
83
+ elif opt_type == 'put':
84
+ return (base_theta + (r * K * np.exp(-r * T) * norm.cdf(-d2)))/365
85
+ else:
86
+ raise ValueError("Valid types are 'call' and 'put'")
87
+
88
+ @staticmethod
89
+ def rho(option: Option) -> float:
90
+ """Per 1% change in interest rate"""
91
+ S = option.S
92
+ K = option.K
93
+ T = option.T
94
+ r = option.r
95
+ sigma = option.sigma
96
+ opt_type = option.opt_type
97
+
98
+ d1 = (np.log(S / K) + (r + (sigma**2)/2)*T)/(sigma*np.sqrt(T))
99
+ d2 = d1 - sigma*np.sqrt(T)
100
+
101
+ if opt_type == 'call':
102
+ return K * T * np.exp(-r*T) * norm.cdf(d2) / 100
103
+ elif opt_type == 'put':
104
+ return -K * T * np.exp(-r * T) * norm.cdf(-d2) / 100
105
+ else:
106
+ raise ValueError("Valid types are 'call' and 'put'")
107
+
@@ -0,0 +1,44 @@
1
+ import numpy as np
2
+ from options.option import Option
3
+
4
+ class HestonPricer:
5
+ """Heston model for option pricing."""
6
+ @staticmethod
7
+ def price(
8
+ option: Option,
9
+ kappa: float,
10
+ theta: float,
11
+ sigma: float,
12
+ rho: float, v0:
13
+ float,
14
+ n_paths: int = 10000,
15
+ n_steps: int = 252) -> float:
16
+ S0 = option.S
17
+ K = option.K
18
+ T = option.T
19
+ r = option.r
20
+ opt_type = option.opt_type
21
+
22
+ dt = T / n_steps
23
+
24
+ dW1 = np.random.normal(0, np.sqrt(dt), (n_paths, n_steps))
25
+ dW2 = rho * dW1 + np.sqrt(1 - rho**2) * np.random.normal(0, np.sqrt(dt), (n_paths, n_steps))
26
+
27
+ S = np.zeros((n_paths, n_steps + 1))
28
+ v = np.zeros((n_paths, n_steps + 1))
29
+
30
+ S[:, 0] = S0
31
+ v[:, 0] = v0
32
+
33
+ for i in range(1, n_steps + 1):
34
+ S[:, i] = S[:, i - 1] * np.exp((r - 0.5 * v[:, i - 1]) * dt + np.sqrt(v[:, i - 1]) * dW1[:, i - 1])
35
+ sqrt_v = np.sqrt(np.maximum(v[:, i-1], 0))
36
+ v_next = (v[:, i-1] + kappa*(theta - v[:, i-1])*dt + sigma*sqrt_v*dW2[:, i-1])
37
+ v[:, i] = np.maximum(v_next, 0)
38
+
39
+ S_t = S[:, -1]
40
+ payoff = np.maximum(S_t - K, 0) if opt_type == 'call' else np.maximum(K - S_t, 0)
41
+ price = np.exp(-r * T) * np.mean(payoff)
42
+ return price
43
+
44
+
@@ -0,0 +1,33 @@
1
+ from processes.gbm import GeometricBrownianMotion
2
+ import numpy as np
3
+ from options.option import Option
4
+
5
+ class MonteCarloPricer:
6
+ """Monte Carlo pricing for European and Asian options."""
7
+ @staticmethod
8
+ def price_european(option: Option, n_paths: int = 10000, n_steps: int = 252) -> float:
9
+ gbm = GeometricBrownianMotion(S0=option.S, mu=option.r, sigma=option.sigma)
10
+ paths = gbm.sim_paths(T=option.T, dt=option.T/n_steps, n_paths=n_paths)
11
+ S_t = paths[:, -1]
12
+
13
+ if option.opt_type == 'call':
14
+ payoffs = np.maximum(S_t - option.K, 0)
15
+ else:
16
+ payoffs = np.maximum(option.K - S_t, 0)
17
+
18
+ price = np.exp(-option.r * option.T) * np.mean(payoffs)
19
+ return price
20
+
21
+ @staticmethod
22
+ def price_asian(option: Option, n_paths: int = 10000, n_steps: int = 252) -> float:
23
+ gbm = GeometricBrownianMotion(S0=option.S, mu=option.r, sigma=option.sigma)
24
+ paths = gbm.sim_paths(T=option.T, dt=option.T/n_steps, n_paths=n_paths)
25
+ path_avg = np.mean(paths, axis=1)
26
+
27
+ if option.opt_type == 'call':
28
+ payoffs = np.maximum(path_avg - option.K, 0)
29
+ else:
30
+ payoffs = np.maximum(option.K - path_avg, 0)
31
+
32
+ price = np.exp(-option.r * option.T) * np.mean(payoffs)
33
+ return price
@@ -0,0 +1,37 @@
1
+ import numpy as np
2
+ from options.option import Option
3
+
4
+ class BinomialTreePricer:
5
+ """Binomial tree pricing for American options."""
6
+ @staticmethod
7
+ def price_american(option: Option, u: float, d: float, n_steps: int = 100) -> float:
8
+ S0 = option.S
9
+ K = option.K
10
+ T = option.T
11
+ r = option.r
12
+ opt_type = option.opt_type
13
+
14
+ dt = T / n_steps
15
+ q = (np.exp(r * dt) - d) / (u - d)
16
+ discount = np.exp(-r * dt)
17
+
18
+ asset_prices = np.zeros(n_steps + 1)
19
+ for i in range(n_steps + 1):
20
+ asset_prices[i] = S0 * (u ** i) * (d ** (n_steps - i))
21
+
22
+ option_values = np.zeros(n_steps + 1)
23
+ for i in range(n_steps + 1):
24
+ if opt_type == 'call':
25
+ option_values[i] = np.maximum(asset_prices[i] - K, 0)
26
+ else:
27
+ option_values[i] = np.maximum(K - asset_prices[i], 0)
28
+
29
+ for i in np.arange(n_steps - 1, -1, -1):
30
+ for j in range(i + 1):
31
+ option_values[j] = discount * (q * option_values[j + 1] + (1 - q) * option_values[j])
32
+ asset_price = S0 * (u ** j) * (d ** (i - j))
33
+ if opt_type == 'call':
34
+ option_values[j] = np.maximum(option_values[j], asset_price - K)
35
+ else:
36
+ option_values[j] = np.maximum(option_values[j], K - asset_price)
37
+ return option_values[0]
@@ -0,0 +1,6 @@
1
+ from .gbm import GeometricBrownianMotion
2
+ from .abm import ArithmeticBrownianMotion
3
+ from .jump_diffusion import JumpDiffusion
4
+ from .ou import OrnsteinUhlenbeck
5
+ from .vasicek import VasicekModel
6
+ from .cir import CoxIngersollRoss
@@ -0,0 +1,44 @@
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+
4
+ class ArithmeticBrownianMotion:
5
+ """Arithmetic Brownian Motion (ABM) process."""
6
+ def __init__(self, S0: float, mu: float, sigma: float):
7
+ self.S0 = S0
8
+ self.mu = mu
9
+ self.sigma = sigma
10
+
11
+ def sim_paths(self, dt: float, T: float, n_paths: int) -> np.ndarray:
12
+ n_steps = int(T / dt)
13
+ paths = np.zeros((n_paths, n_steps + 1))
14
+
15
+ dW = np.random.normal(0, 1, (n_paths, n_steps))
16
+ paths[:, 0] = self.S0
17
+
18
+ for i in range(1, n_steps + 1):
19
+ paths[:, i] = paths[:, i-1] + self.mu*dt + self.sigma*np.sqrt(dt)*dW[:, i - 1]
20
+
21
+ return paths
22
+
23
+ def plot_paths(self, paths, title='ABM Simulated Paths', xlabel='Steps', ylabel='Price', average=True, largest=True, smallest=True):
24
+ fig, ax = plt.subplots()
25
+
26
+ for path in paths:
27
+ ax.plot(path, color='#1f77b4', linewidth=0.75, alpha=0.5)
28
+
29
+ average_ = np.mean(paths, axis=0)
30
+ largest_ = np.max(paths, axis=0)
31
+ smallest_ = np.min(paths, axis=0)
32
+ if average:
33
+ ax.plot(average_, color='purple', linewidth=0.8, alpha=0.4)
34
+ if largest:
35
+ ax.plot(largest_, color='green', linewidth=0.8, alpha=0.4)
36
+ if smallest:
37
+ ax.plot(smallest_, color="red", linewidth=0.8, alpha=0.4)
38
+
39
+ ax.set_title(title)
40
+ ax.set_xlabel(xlabel)
41
+ ax.set_ylabel(ylabel)
42
+ plt.show()
43
+
44
+
@@ -0,0 +1,46 @@
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+
4
+ class CoxIngersollRoss:
5
+ """Cox-Ingersoll-Ross (CIR) process."""
6
+ def __init__(self, r0: float, a: float, b: float, sigma: float):
7
+ self.r0 = r0
8
+ self.a = a
9
+ self.b = b
10
+ self.sigma = sigma
11
+
12
+ def sim_paths(self, T: float, dt: float, n_paths: int) -> np.ndarray:
13
+ n_steps = int(T / dt)
14
+ paths = np.zeros((n_paths, n_steps + 1))
15
+ paths[:, 0] = self.r0
16
+
17
+ sqrt_dt = np.sqrt(dt)
18
+ for i in range(1, n_steps + 1):
19
+ r_prev = paths[:, i - 1]
20
+ z = np.random.normal(size=n_paths)
21
+ dr = (self.a * (self.b - r_prev) * dt
22
+ + self.sigma * np.sqrt(r_prev) * sqrt_dt * z)
23
+ paths[:, i] = np.maximum(r_prev + dr, 0)
24
+
25
+ return paths
26
+
27
+ def plot_paths(self, paths, title='CIR Simulated Paths', xlabel='Steps', ylabel='Rate', average=True, largest=True, smallest=True):
28
+ fig, ax = plt.subplots()
29
+
30
+ for path in paths:
31
+ ax.plot(path, color='#1f77b4', linewidth=0.75, alpha=0.5)
32
+
33
+ average_ = np.mean(paths, axis=0)
34
+ largest_ = np.max(paths, axis=0)
35
+ smallest_ = np.min(paths, axis=0)
36
+ if average:
37
+ ax.plot(average_, color='purple', linewidth=0.8, alpha=0.4)
38
+ if largest:
39
+ ax.plot(largest_, color='green', linewidth=0.8, alpha=0.4)
40
+ if smallest:
41
+ ax.plot(smallest_, color="red", linewidth=0.8, alpha=0.4)
42
+
43
+ ax.set_title(title)
44
+ ax.set_xlabel(xlabel)
45
+ ax.set_ylabel(ylabel)
46
+ plt.show()
@@ -0,0 +1,44 @@
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+
4
+ class GeometricBrownianMotion:
5
+ """Geometric Brownian Motion (GBM) process."""
6
+ def __init__(self, S0: float, mu: float, sigma: float):
7
+ self.S0 = S0
8
+ self.mu = mu
9
+ self.sigma = sigma
10
+
11
+
12
+ def sim_paths(self, T: float, dt: float, n_paths: int) -> np.ndarray:
13
+ n_steps = int(T / dt)
14
+ paths = np.zeros((n_paths, n_steps + 1))
15
+ paths[:, 0] = self.S0
16
+
17
+ dW = np.random.normal(0, np.sqrt(dt), (n_paths, n_steps))
18
+
19
+ for i in range(1, n_steps + 1):
20
+ paths[:, i] = paths[:, i-1] * np.exp(
21
+ (self.mu - 0.5*self.sigma**2)*dt + self.sigma*dW[:, i-1]
22
+ )
23
+ return paths
24
+
25
+ def plot_paths(self, paths, title='GBM Simulated Paths', xlabel='Steps', ylabel='Price', average=True, largest=True, smallest=True):
26
+ fig, ax = plt.subplots()
27
+
28
+ for path in paths:
29
+ ax.plot(path, color='#1f77b4', linewidth=0.75, alpha=0.5)
30
+
31
+ average_ = np.mean(paths, axis=0)
32
+ largest_ = np.max(paths, axis=0)
33
+ smallest_ = np.min(paths, axis=0)
34
+ if average:
35
+ ax.plot(average_, color='purple', linewidth=0.8, alpha=0.4)
36
+ if largest:
37
+ ax.plot(largest_, color='green', linewidth=0.8, alpha=0.4)
38
+ if smallest:
39
+ ax.plot(smallest_, color="red", linewidth=0.8, alpha=0.4)
40
+
41
+ ax.set_title(title)
42
+ ax.set_xlabel(xlabel)
43
+ ax.set_ylabel(ylabel)
44
+ plt.show()
@@ -0,0 +1,54 @@
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+
4
+ class JumpDiffusion:
5
+ """Jump Diffusion process."""
6
+ def __init__(self, S0: float, mu: float, sigma: float, lambda_j: float, mu_j: float, sigma_j: float):
7
+ self.S0 = S0
8
+ self.mu = mu
9
+ self.sigma = sigma
10
+ self.lambda_j = lambda_j
11
+ self.mu_j = mu_j
12
+ self.sigma_j = sigma_j
13
+
14
+ def sim_paths(self, T: float, dt: float, n_paths: int) -> np.ndarray:
15
+ n_steps = int(T / dt)
16
+ paths = np.zeros((n_paths, n_steps + 1))
17
+ paths[:, 0] = self.S0
18
+
19
+ dW = np.random.normal(0, np.sqrt(dt), (n_paths, n_steps))
20
+
21
+ n_jumps = np.random.poisson(self.lambda_j * dt, (n_paths, n_steps))
22
+ jump_mean = n_jumps * self.mu_j
23
+ jump_std = np.sqrt(n_jumps) * self.sigma_j
24
+ log_jumps = np.random.normal(jump_mean, np.where(jump_std > 0, jump_std, 1e-12))
25
+ log_jumps = np.where(n_jumps > 0, log_jumps, 0)
26
+
27
+
28
+
29
+ for i in range(1, n_steps + 1):
30
+ paths[:, i] = paths[:, i-1] * np.exp(
31
+ (self.mu - 0.5*self.sigma**2)*dt + self.sigma*dW[:, i-1] + log_jumps[:, i-1]
32
+ )
33
+ return paths
34
+
35
+ def plot_paths(self, paths, title='Jump Diffusion Simulated Paths', xlabel='Steps', ylabel='Price', average=True, largest=True, smallest=True):
36
+ fig, ax = plt.subplots()
37
+
38
+ for path in paths:
39
+ ax.plot(path, color='#1f77b4', linewidth=0.75, alpha=0.5)
40
+
41
+ average_ = np.mean(paths, axis=0)
42
+ largest_ = np.max(paths, axis=0)
43
+ smallest_ = np.min(paths, axis=0)
44
+ if average:
45
+ ax.plot(average_, color='purple', linewidth=0.8, alpha=0.4)
46
+ if largest:
47
+ ax.plot(largest_, color='green', linewidth=0.8, alpha=0.4)
48
+ if smallest:
49
+ ax.plot(smallest_, color="red", linewidth=0.8, alpha=0.4)
50
+
51
+ ax.set_title(title)
52
+ ax.set_xlabel(xlabel)
53
+ ax.set_ylabel(ylabel)
54
+ plt.show()
@@ -0,0 +1,46 @@
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+
4
+ class OrnsteinUhlenbeck:
5
+ """Ornstein-Uhlenbeck (OU) process."""
6
+ def __init__(self, X0: float, theta: float, mu: float, sigma: float, T: float, dt: float):
7
+ self.X0 = X0
8
+ self.theta = theta
9
+ self.mu = mu
10
+ self.sigma = sigma
11
+ self.T = T
12
+ self.dt = dt
13
+
14
+ def process(self, n_paths: int) -> np.ndarray:
15
+ n_steps = int(self.T / self.dt)
16
+
17
+ paths = np.zeros((n_paths, n_steps + 1))
18
+ paths[:, 0] = self.X0
19
+
20
+ dW = np.random.normal(0, np.sqrt(self.dt), (n_paths, n_steps))
21
+ for i in range(1, n_steps + 1):
22
+ paths[:, i] = paths[:, i-1] + self.theta * (self.mu - paths[:, i-1]) * self.dt + self.sigma * dW[:, i-1]
23
+
24
+
25
+ return paths
26
+
27
+ def plot_paths(self, paths, title='Ornstein-Uhlenbeck Simulated Paths', xlabel='Steps', ylabel='Value', average=True, largest=True, smallest=True):
28
+ fig, ax = plt.subplots()
29
+ for path in paths:
30
+ ax.plot(path, color='#1f77b4', linewidth=0.8, alpha=0.5)
31
+
32
+ average_ = np.mean(paths, axis=0)
33
+ largest_ = np.max(paths, axis=0)
34
+ smallest_ = np.min(paths, axis=0)
35
+ if average:
36
+ ax.plot(average_, color='purple', linewidth=0.8, alpha=0.4)
37
+ if largest:
38
+ ax.plot(largest_, color='green', linewidth=0.8, alpha=0.4)
39
+ if smallest:
40
+ ax.plot(smallest_, color="red", linewidth=0.8, alpha=0.4)
41
+
42
+ ax.set_title(title)
43
+ ax.set_xlabel(xlabel)
44
+ ax.set_ylabel(ylabel)
45
+ plt.show()
46
+
@@ -0,0 +1,48 @@
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+
4
+ class VasicekModel:
5
+ """Vasicek model for interest rate simulation."""
6
+ def __init__(self, theta: float, mu: float, sigma: float, r0: float):
7
+ self.theta = theta
8
+ self.mu = mu
9
+ self.sigma = sigma
10
+ self.r0 = r0
11
+
12
+ def sim_paths(self, T: float, dt: float, n_paths: int) -> np.ndarray:
13
+ n_steps = int(T / dt)
14
+ paths = np.zeros((n_paths, n_steps + 1))
15
+ paths[:, 0] = self.r0
16
+
17
+ sqrt_dt = np.sqrt(dt)
18
+ for i in range(1, n_steps + 1):
19
+ r_prev = paths[:, i - 1]
20
+ z = np.random.normal(size=n_paths)
21
+ dr = self.theta * (self.mu - r_prev) * dt + self.sigma * sqrt_dt * z
22
+ paths[:, i] = r_prev + dr
23
+
24
+ return paths
25
+
26
+ def plot_paths(self, paths, T, dt, title='Simulated Vasicek Interest Rate Paths', xlabel='Time', ylabel='Interest Rate', average=True, largest=True, smallest=True):
27
+ n_steps = int(T / dt)
28
+ time_points = np.linspace(0, T, n_steps + 1)
29
+
30
+ fig, ax = plt.subplots()
31
+ for path in paths:
32
+ ax.plot(time_points, path, color='#1f77b4', linewidth=0.75, alpha=0.5)
33
+
34
+ average_ = np.mean(paths, axis=0)
35
+ largest_ = np.max(paths, axis=0)
36
+ smallest_ = np.min(paths, axis=0)
37
+ if average:
38
+ ax.plot(time_points, average_, color='purple', linewidth=0.8, alpha=0.4)
39
+ if largest:
40
+ ax.plot(time_points, largest_, color='green', linewidth=0.8, alpha=0.4)
41
+ if smallest:
42
+ ax.plot(time_points, smallest_, color="red", linewidth=0.8, alpha=0.4)
43
+
44
+ ax.set_title(title)
45
+ ax.set_xlabel(xlabel)
46
+ ax.set_ylabel(ylabel)
47
+ ax.set_xlim(0, T)
48
+ plt.show()
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "finmat"
7
+ version = "0.1.0"
8
+ description = "A financial math library for options pricing, stochastic processes, and fixed income analytics"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{name = "Daniel Butler", email = "danielbutler245@gmail.com"}]
14
+ dependencies = [
15
+ "numpy",
16
+ "scipy",
17
+ "matplotlib",
18
+ ]
19
+
20
+ [tool.setuptools.packages.find]
21
+ where = ["."]
22
+ include = ["fixed_income*", "options*", "portfolio*", "pricing*", "processes*"]
23
+ namespaces = false
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/dbu79/fin-mat-library"
finmat-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+