FinStoch 0.0.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.
- finstoch-0.0.0/.github/workflows/cd.yml +40 -0
- finstoch-0.0.0/.github/workflows/ci.yml +46 -0
- finstoch-0.0.0/.gitignore +35 -0
- finstoch-0.0.0/FinStoch/__init__.py +35 -0
- finstoch-0.0.0/FinStoch/processes/__init__.py +19 -0
- finstoch-0.0.0/FinStoch/processes/base.py +202 -0
- finstoch-0.0.0/FinStoch/processes/cev.py +90 -0
- finstoch-0.0.0/FinStoch/processes/cir.py +91 -0
- finstoch-0.0.0/FinStoch/processes/gbm.py +59 -0
- finstoch-0.0.0/FinStoch/processes/heston.py +169 -0
- finstoch-0.0.0/FinStoch/processes/merton.py +118 -0
- finstoch-0.0.0/FinStoch/processes/ou.py +88 -0
- finstoch-0.0.0/FinStoch/py.typed +0 -0
- finstoch-0.0.0/FinStoch/utils/__init__.py +0 -0
- finstoch-0.0.0/FinStoch/utils/plotting.py +60 -0
- finstoch-0.0.0/FinStoch/utils/random.py +44 -0
- finstoch-0.0.0/FinStoch/utils/timesteps.py +108 -0
- finstoch-0.0.0/FinStoch.egg-info/PKG-INFO +390 -0
- finstoch-0.0.0/FinStoch.egg-info/SOURCES.txt +39 -0
- finstoch-0.0.0/FinStoch.egg-info/dependency_links.txt +1 -0
- finstoch-0.0.0/FinStoch.egg-info/requires.txt +13 -0
- finstoch-0.0.0/FinStoch.egg-info/top_level.txt +1 -0
- finstoch-0.0.0/LICENSE +21 -0
- finstoch-0.0.0/PKG-INFO +390 -0
- finstoch-0.0.0/README.md +354 -0
- finstoch-0.0.0/image/cev.png +0 -0
- finstoch-0.0.0/image/cir.png +0 -0
- finstoch-0.0.0/image/gbm.png +0 -0
- finstoch-0.0.0/image/heston_val.png +0 -0
- finstoch-0.0.0/image/heston_vol.png +0 -0
- finstoch-0.0.0/image/merton.png +0 -0
- finstoch-0.0.0/image/ou.png +0 -0
- finstoch-0.0.0/pyproject.toml +61 -0
- finstoch-0.0.0/setup.cfg +4 -0
- finstoch-0.0.0/tests/business_days_test.py +169 -0
- finstoch-0.0.0/tests/cev_test.py +94 -0
- finstoch-0.0.0/tests/cir_test.py +94 -0
- finstoch-0.0.0/tests/gbm_test.py +86 -0
- finstoch-0.0.0/tests/heston_test.py +127 -0
- finstoch-0.0.0/tests/merton_test.py +92 -0
- finstoch-0.0.0/tests/ou_test.py +94 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: CD
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
id-token: write
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
with:
|
|
18
|
+
fetch-depth: 0 # Required for setuptools-scm to read git tags
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.12"
|
|
22
|
+
- run: pip install build
|
|
23
|
+
- run: python -m build
|
|
24
|
+
- uses: actions/upload-artifact@v4
|
|
25
|
+
with:
|
|
26
|
+
name: dist
|
|
27
|
+
path: dist/
|
|
28
|
+
|
|
29
|
+
publish:
|
|
30
|
+
needs: build
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
environment: pypi
|
|
33
|
+
permissions:
|
|
34
|
+
id-token: write
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/download-artifact@v4
|
|
37
|
+
with:
|
|
38
|
+
name: dist
|
|
39
|
+
path: dist/
|
|
40
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
with:
|
|
15
|
+
fetch-depth: 0
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.12"
|
|
19
|
+
- run: pip install -e ".[dev]"
|
|
20
|
+
- name: Check formatting
|
|
21
|
+
run: ruff format --check .
|
|
22
|
+
- name: Lint (critical errors)
|
|
23
|
+
run: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
|
24
|
+
- name: Lint (warnings)
|
|
25
|
+
run: flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
|
26
|
+
- name: Type check
|
|
27
|
+
run: mypy . --exclude venv --exclude build --exclude dist --ignore-missing-imports
|
|
28
|
+
|
|
29
|
+
test:
|
|
30
|
+
needs: lint
|
|
31
|
+
runs-on: ${{ matrix.os }}
|
|
32
|
+
strategy:
|
|
33
|
+
fail-fast: false
|
|
34
|
+
matrix:
|
|
35
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
36
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v4
|
|
39
|
+
with:
|
|
40
|
+
fetch-depth: 0
|
|
41
|
+
- uses: actions/setup-python@v5
|
|
42
|
+
with:
|
|
43
|
+
python-version: ${{ matrix.python-version }}
|
|
44
|
+
- run: pip install -e ".[dev]"
|
|
45
|
+
- name: Run tests
|
|
46
|
+
run: python -m unittest discover -s tests -p "*_test.py" -v
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
**/__pycache__/
|
|
4
|
+
*.pyc
|
|
5
|
+
*.pyo
|
|
6
|
+
|
|
7
|
+
# Build & distribution
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
*.egg-info/
|
|
11
|
+
|
|
12
|
+
# Virtual environments
|
|
13
|
+
venv/
|
|
14
|
+
.venv/
|
|
15
|
+
|
|
16
|
+
# IDE
|
|
17
|
+
.vscode/
|
|
18
|
+
|
|
19
|
+
# Testing & linting caches
|
|
20
|
+
.pytest_cache/
|
|
21
|
+
.mypy_cache/
|
|
22
|
+
.ruff_cache/
|
|
23
|
+
|
|
24
|
+
# Generated version file
|
|
25
|
+
FinStoch/_version.py
|
|
26
|
+
|
|
27
|
+
# Claude Code (local only)
|
|
28
|
+
.claude/
|
|
29
|
+
|
|
30
|
+
# Project-specific
|
|
31
|
+
dependencies.txt
|
|
32
|
+
dependencies.json
|
|
33
|
+
*.ipynb
|
|
34
|
+
notes.txt
|
|
35
|
+
CLAUDE.md
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""FinStoch — A Python library for simulating stochastic processes in finance."""
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
5
|
+
|
|
6
|
+
__version__ = version("FinStoch")
|
|
7
|
+
except PackageNotFoundError:
|
|
8
|
+
try:
|
|
9
|
+
from FinStoch._version import version as __version__ # type: ignore[no-redef]
|
|
10
|
+
except ImportError:
|
|
11
|
+
__version__ = "0.0.0-unknown"
|
|
12
|
+
|
|
13
|
+
__author__ = "Yosri Ben Halima"
|
|
14
|
+
__email__ = "yosri.benhalima@ept.ucar.tn"
|
|
15
|
+
__license__ = "MIT"
|
|
16
|
+
|
|
17
|
+
from FinStoch.processes import (
|
|
18
|
+
StochasticProcess,
|
|
19
|
+
GeometricBrownianMotion,
|
|
20
|
+
MertonJumpDiffusion,
|
|
21
|
+
OrnsteinUhlenbeck,
|
|
22
|
+
CoxIngersollRoss,
|
|
23
|
+
HestonModel,
|
|
24
|
+
ConstantElasticityOfVariance,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"StochasticProcess",
|
|
29
|
+
"GeometricBrownianMotion",
|
|
30
|
+
"MertonJumpDiffusion",
|
|
31
|
+
"OrnsteinUhlenbeck",
|
|
32
|
+
"CoxIngersollRoss",
|
|
33
|
+
"HestonModel",
|
|
34
|
+
"ConstantElasticityOfVariance",
|
|
35
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Stochastic process simulators."""
|
|
2
|
+
|
|
3
|
+
from .base import StochasticProcess
|
|
4
|
+
from .gbm import GeometricBrownianMotion
|
|
5
|
+
from .merton import MertonJumpDiffusion
|
|
6
|
+
from .ou import OrnsteinUhlenbeck
|
|
7
|
+
from .cir import CoxIngersollRoss
|
|
8
|
+
from .heston import HestonModel
|
|
9
|
+
from .cev import ConstantElasticityOfVariance
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"StochasticProcess",
|
|
13
|
+
"GeometricBrownianMotion",
|
|
14
|
+
"MertonJumpDiffusion",
|
|
15
|
+
"OrnsteinUhlenbeck",
|
|
16
|
+
"CoxIngersollRoss",
|
|
17
|
+
"HestonModel",
|
|
18
|
+
"ConstantElasticityOfVariance",
|
|
19
|
+
]
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Base class for all stochastic process simulators."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Union
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from pandas import DatetimeIndex
|
|
8
|
+
|
|
9
|
+
from FinStoch.utils.plotting import plot_simulated_paths
|
|
10
|
+
from FinStoch.utils.timesteps import (
|
|
11
|
+
generate_date_range_with_granularity,
|
|
12
|
+
date_range_duration,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class StochasticProcess(ABC):
|
|
17
|
+
"""Abstract base class for stochastic process simulators.
|
|
18
|
+
|
|
19
|
+
Provides shared initialization, time grid management, and plotting
|
|
20
|
+
for all Euler-Maruyama discretized stochastic processes.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
S0 : float
|
|
25
|
+
The initial value of the process.
|
|
26
|
+
mu : float
|
|
27
|
+
The drift coefficient.
|
|
28
|
+
sigma : float
|
|
29
|
+
The volatility coefficient.
|
|
30
|
+
num_paths : int
|
|
31
|
+
The number of paths to simulate.
|
|
32
|
+
start_date : str
|
|
33
|
+
The start date for the simulation (e.g., '2023-09-01').
|
|
34
|
+
end_date : str
|
|
35
|
+
The end date for the simulation (e.g., '2023-12-31').
|
|
36
|
+
granularity : str
|
|
37
|
+
The time granularity for each step (e.g., 'D', 'H', '10T').
|
|
38
|
+
business_days : bool, optional
|
|
39
|
+
If True, use business days instead of calendar days when
|
|
40
|
+
granularity is 'D'. Default is False.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
S0: float,
|
|
46
|
+
mu: float,
|
|
47
|
+
sigma: float,
|
|
48
|
+
num_paths: int,
|
|
49
|
+
start_date: str,
|
|
50
|
+
end_date: str,
|
|
51
|
+
granularity: str,
|
|
52
|
+
business_days: bool = False,
|
|
53
|
+
) -> None:
|
|
54
|
+
self._S0 = S0
|
|
55
|
+
self._mu = mu
|
|
56
|
+
self._sigma = sigma
|
|
57
|
+
self._num_paths = num_paths
|
|
58
|
+
self._start_date = start_date
|
|
59
|
+
self._end_date = end_date
|
|
60
|
+
self._granularity = granularity
|
|
61
|
+
self._business_days = business_days
|
|
62
|
+
self._recalculate_time_grid()
|
|
63
|
+
|
|
64
|
+
def _recalculate_time_grid(self) -> None:
|
|
65
|
+
"""Recompute time grid attributes from date range and granularity."""
|
|
66
|
+
self._t = generate_date_range_with_granularity(
|
|
67
|
+
self._start_date, self._end_date, self._granularity, self._business_days
|
|
68
|
+
)
|
|
69
|
+
self._T = date_range_duration(self._t)
|
|
70
|
+
self._num_steps = len(self._t)
|
|
71
|
+
self._dt = self._T / self._num_steps
|
|
72
|
+
|
|
73
|
+
@abstractmethod
|
|
74
|
+
def simulate(self) -> Union[np.ndarray, tuple[np.ndarray, np.ndarray]]:
|
|
75
|
+
"""Simulate paths of the stochastic process.
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
np.ndarray or tuple[np.ndarray, np.ndarray]
|
|
80
|
+
A 2D array of shape (num_paths, num_steps), or a tuple of two
|
|
81
|
+
such arrays for models with multiple outputs (e.g., Heston).
|
|
82
|
+
"""
|
|
83
|
+
...
|
|
84
|
+
|
|
85
|
+
def plot(
|
|
86
|
+
self,
|
|
87
|
+
paths: np.ndarray | None = None,
|
|
88
|
+
title: str = "Simulated Paths",
|
|
89
|
+
ylabel: str = "Value",
|
|
90
|
+
fig_size: tuple | None = None,
|
|
91
|
+
**kwargs: object,
|
|
92
|
+
) -> None:
|
|
93
|
+
"""Plot simulated paths.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
paths : np.ndarray, optional
|
|
98
|
+
Pre-computed paths to plot. If None, calls simulate().
|
|
99
|
+
title : str
|
|
100
|
+
Plot title.
|
|
101
|
+
ylabel : str
|
|
102
|
+
Y-axis label.
|
|
103
|
+
fig_size : tuple, optional
|
|
104
|
+
Figure size in inches.
|
|
105
|
+
**kwargs
|
|
106
|
+
Additional keyword arguments passed to plot_simulated_paths.
|
|
107
|
+
"""
|
|
108
|
+
plot_simulated_paths(
|
|
109
|
+
self._t,
|
|
110
|
+
self.simulate,
|
|
111
|
+
paths,
|
|
112
|
+
title=title,
|
|
113
|
+
ylabel=ylabel,
|
|
114
|
+
fig_size=fig_size,
|
|
115
|
+
grid=kwargs.get("grid", True),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# --- Shared properties ---
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def S0(self) -> float:
|
|
122
|
+
return self._S0
|
|
123
|
+
|
|
124
|
+
@S0.setter
|
|
125
|
+
def S0(self, value: float) -> None:
|
|
126
|
+
self._S0 = value
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def mu(self) -> float:
|
|
130
|
+
return self._mu
|
|
131
|
+
|
|
132
|
+
@mu.setter
|
|
133
|
+
def mu(self, value: float) -> None:
|
|
134
|
+
self._mu = value
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def sigma(self) -> float:
|
|
138
|
+
return self._sigma
|
|
139
|
+
|
|
140
|
+
@sigma.setter
|
|
141
|
+
def sigma(self, value: float) -> None:
|
|
142
|
+
self._sigma = value
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def T(self) -> float:
|
|
146
|
+
return self._T
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def num_steps(self) -> int:
|
|
150
|
+
return self._num_steps
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def num_paths(self) -> int:
|
|
154
|
+
return self._num_paths
|
|
155
|
+
|
|
156
|
+
@num_paths.setter
|
|
157
|
+
def num_paths(self, value: int) -> None:
|
|
158
|
+
self._num_paths = value
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def dt(self) -> float:
|
|
162
|
+
return self._dt
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def t(self) -> DatetimeIndex:
|
|
166
|
+
return self._t
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def start_date(self) -> str:
|
|
170
|
+
return self._start_date
|
|
171
|
+
|
|
172
|
+
@start_date.setter
|
|
173
|
+
def start_date(self, value: str) -> None:
|
|
174
|
+
self._start_date = value
|
|
175
|
+
self._recalculate_time_grid()
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def end_date(self) -> str:
|
|
179
|
+
return self._end_date
|
|
180
|
+
|
|
181
|
+
@end_date.setter
|
|
182
|
+
def end_date(self, value: str) -> None:
|
|
183
|
+
self._end_date = value
|
|
184
|
+
self._recalculate_time_grid()
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def granularity(self) -> str:
|
|
188
|
+
return self._granularity
|
|
189
|
+
|
|
190
|
+
@granularity.setter
|
|
191
|
+
def granularity(self, value: str) -> None:
|
|
192
|
+
self._granularity = value
|
|
193
|
+
self._recalculate_time_grid()
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def business_days(self) -> bool:
|
|
197
|
+
return self._business_days
|
|
198
|
+
|
|
199
|
+
@business_days.setter
|
|
200
|
+
def business_days(self, value: bool) -> None:
|
|
201
|
+
self._business_days = value
|
|
202
|
+
self._recalculate_time_grid()
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Constant Elasticity of Variance process."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from FinStoch.processes.base import StochasticProcess
|
|
6
|
+
from FinStoch.utils.random import generate_random_numbers
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ConstantElasticityOfVariance(StochasticProcess):
|
|
10
|
+
"""Constant Elasticity of Variance (CEV) process simulator.
|
|
11
|
+
|
|
12
|
+
Models an asset price following the SDE:
|
|
13
|
+
dS = mu * S * dt + sigma * S^gamma * dW
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
S0 : float
|
|
18
|
+
The initial value of the asset.
|
|
19
|
+
mu : float
|
|
20
|
+
The annualized drift coefficient.
|
|
21
|
+
sigma : float
|
|
22
|
+
The annualized volatility coefficient.
|
|
23
|
+
gamma : float
|
|
24
|
+
The elasticity parameter.
|
|
25
|
+
num_paths : int
|
|
26
|
+
The number of paths to simulate.
|
|
27
|
+
start_date : str
|
|
28
|
+
The start date for the simulation.
|
|
29
|
+
end_date : str
|
|
30
|
+
The end date for the simulation.
|
|
31
|
+
granularity : str
|
|
32
|
+
The time granularity for each step.
|
|
33
|
+
business_days : bool, optional
|
|
34
|
+
If True, use business days instead of calendar days. Default is False.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
S0: float,
|
|
40
|
+
mu: float,
|
|
41
|
+
sigma: float,
|
|
42
|
+
gamma: float,
|
|
43
|
+
num_paths: int,
|
|
44
|
+
start_date: str,
|
|
45
|
+
end_date: str,
|
|
46
|
+
granularity: str,
|
|
47
|
+
business_days: bool = False,
|
|
48
|
+
) -> None:
|
|
49
|
+
self._gamma = gamma
|
|
50
|
+
super().__init__(S0, mu, sigma, num_paths, start_date, end_date, granularity, business_days)
|
|
51
|
+
|
|
52
|
+
def simulate(self) -> np.ndarray:
|
|
53
|
+
"""Simulate paths of the CEV model.
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
np.ndarray
|
|
58
|
+
A 2D array of shape (num_paths, num_steps).
|
|
59
|
+
"""
|
|
60
|
+
S = np.zeros((self._num_paths, self._num_steps))
|
|
61
|
+
S[:, 0] = self._S0
|
|
62
|
+
|
|
63
|
+
for t in range(1, self._num_steps):
|
|
64
|
+
Z = generate_random_numbers("normal", self._num_paths, mean=0, stddev=1)
|
|
65
|
+
S[:, t] = (
|
|
66
|
+
S[:, t - 1]
|
|
67
|
+
+ self._mu * S[:, t - 1] * self._dt
|
|
68
|
+
+ self._sigma * (S[:, t - 1] ** self._gamma) * np.sqrt(self._dt) * Z
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return S
|
|
72
|
+
|
|
73
|
+
def plot(
|
|
74
|
+
self,
|
|
75
|
+
paths: np.ndarray | None = None,
|
|
76
|
+
title: str = "Constant Elasticity of Variance",
|
|
77
|
+
ylabel: str = "Value",
|
|
78
|
+
fig_size: tuple | None = None,
|
|
79
|
+
**kwargs: object,
|
|
80
|
+
) -> None:
|
|
81
|
+
"""Plot simulated CEV paths."""
|
|
82
|
+
super().plot(paths, title=title, ylabel=ylabel, fig_size=fig_size, **kwargs)
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def gamma(self) -> float:
|
|
86
|
+
return self._gamma
|
|
87
|
+
|
|
88
|
+
@gamma.setter
|
|
89
|
+
def gamma(self, value: float) -> None:
|
|
90
|
+
self._gamma = value
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Cox-Ingersoll-Ross process."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from FinStoch.processes.base import StochasticProcess
|
|
6
|
+
from FinStoch.utils.random import generate_random_numbers
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CoxIngersollRoss(StochasticProcess):
|
|
10
|
+
"""Cox-Ingersoll-Ross (CIR) mean-reverting process simulator.
|
|
11
|
+
|
|
12
|
+
Models a non-negative process following the SDE:
|
|
13
|
+
dS = theta * (mu - S) * dt + sigma * sqrt(S) * dW
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
S0 : float
|
|
18
|
+
Initial value of the process.
|
|
19
|
+
mu : float
|
|
20
|
+
Long-term mean to which the process reverts.
|
|
21
|
+
sigma : float
|
|
22
|
+
Volatility parameter.
|
|
23
|
+
theta : float
|
|
24
|
+
Speed of reversion to the mean.
|
|
25
|
+
num_paths : int
|
|
26
|
+
Number of simulation paths to generate.
|
|
27
|
+
start_date : str
|
|
28
|
+
Starting date for the simulation.
|
|
29
|
+
end_date : str
|
|
30
|
+
Ending date for the simulation.
|
|
31
|
+
granularity : str
|
|
32
|
+
Granularity of time steps (e.g., '10T' for 10 minutes, 'H' for hours).
|
|
33
|
+
business_days : bool, optional
|
|
34
|
+
If True, use business days instead of calendar days. Default is False.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
S0: float,
|
|
40
|
+
mu: float,
|
|
41
|
+
sigma: float,
|
|
42
|
+
theta: float,
|
|
43
|
+
num_paths: int,
|
|
44
|
+
start_date: str,
|
|
45
|
+
end_date: str,
|
|
46
|
+
granularity: str,
|
|
47
|
+
business_days: bool = False,
|
|
48
|
+
) -> None:
|
|
49
|
+
self._theta = theta
|
|
50
|
+
super().__init__(S0, mu, sigma, num_paths, start_date, end_date, granularity, business_days)
|
|
51
|
+
|
|
52
|
+
def simulate(self) -> np.ndarray:
|
|
53
|
+
"""Simulate paths of the CIR model.
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
np.ndarray
|
|
58
|
+
A 2D array of shape (num_paths, num_steps).
|
|
59
|
+
"""
|
|
60
|
+
S = np.zeros((self._num_paths, self._num_steps))
|
|
61
|
+
S[:, 0] = self._S0
|
|
62
|
+
|
|
63
|
+
for t in range(1, self._num_steps):
|
|
64
|
+
Z = generate_random_numbers("normal", self._num_paths, mean=0, stddev=1)
|
|
65
|
+
drift = self._theta * (self._mu - S[:, t - 1]) * self._dt
|
|
66
|
+
diffusion = self._sigma * np.sqrt(S[:, t - 1]) * np.sqrt(self._dt) * Z
|
|
67
|
+
S[:, t] = S[:, t - 1] + drift + diffusion
|
|
68
|
+
|
|
69
|
+
# Ensure non-negativity
|
|
70
|
+
S[:, t] = np.maximum(S[:, t], 0)
|
|
71
|
+
|
|
72
|
+
return S
|
|
73
|
+
|
|
74
|
+
def plot(
|
|
75
|
+
self,
|
|
76
|
+
paths: np.ndarray | None = None,
|
|
77
|
+
title: str = "Cox-Ingersoll-Ross",
|
|
78
|
+
ylabel: str = "Value",
|
|
79
|
+
fig_size: tuple | None = None,
|
|
80
|
+
**kwargs: object,
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Plot simulated CIR paths."""
|
|
83
|
+
super().plot(paths, title=title, ylabel=ylabel, fig_size=fig_size, **kwargs)
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def theta(self) -> float:
|
|
87
|
+
return self._theta
|
|
88
|
+
|
|
89
|
+
@theta.setter
|
|
90
|
+
def theta(self, value: float) -> None:
|
|
91
|
+
self._theta = value
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Geometric Brownian Motion process."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from FinStoch.processes.base import StochasticProcess
|
|
6
|
+
from FinStoch.utils.random import generate_random_numbers
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GeometricBrownianMotion(StochasticProcess):
|
|
10
|
+
"""Geometric Brownian Motion (GBM) process simulator.
|
|
11
|
+
|
|
12
|
+
Models an asset price following the SDE:
|
|
13
|
+
dS = mu * S * dt + sigma * S * dW
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
S0 : float
|
|
18
|
+
The initial value of the asset.
|
|
19
|
+
mu : float
|
|
20
|
+
The annualized drift coefficient.
|
|
21
|
+
sigma : float
|
|
22
|
+
The annualized volatility coefficient.
|
|
23
|
+
num_paths : int
|
|
24
|
+
The number of paths to simulate.
|
|
25
|
+
start_date : str
|
|
26
|
+
The start date for the simulation (e.g., '2023-09-01').
|
|
27
|
+
end_date : str
|
|
28
|
+
The end date for the simulation (e.g., '2023-12-31').
|
|
29
|
+
granularity : str
|
|
30
|
+
The time granularity for each step (e.g., '10T' for 10 minutes, 'H' for hours).
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def simulate(self) -> np.ndarray:
|
|
34
|
+
"""Simulate paths of the GBM model.
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
np.ndarray
|
|
39
|
+
A 2D array of shape (num_paths, num_steps).
|
|
40
|
+
"""
|
|
41
|
+
S = np.zeros((self._num_paths, self._num_steps))
|
|
42
|
+
S[:, 0] = self._S0
|
|
43
|
+
|
|
44
|
+
for t in range(1, self._num_steps):
|
|
45
|
+
Z = generate_random_numbers("normal", self._num_paths, mean=0, stddev=1)
|
|
46
|
+
S[:, t] = S[:, t - 1] * np.exp((self._mu - 0.5 * self._sigma**2) * self._dt + self._sigma * np.sqrt(self._dt) * Z)
|
|
47
|
+
|
|
48
|
+
return S
|
|
49
|
+
|
|
50
|
+
def plot(
|
|
51
|
+
self,
|
|
52
|
+
paths: np.ndarray | None = None,
|
|
53
|
+
title: str = "Geometric Brownian Motion",
|
|
54
|
+
ylabel: str = "Value",
|
|
55
|
+
fig_size: tuple | None = None,
|
|
56
|
+
**kwargs: object,
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Plot simulated GBM paths."""
|
|
59
|
+
super().plot(paths, title=title, ylabel=ylabel, fig_size=fig_size, **kwargs)
|