pymgarch 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.
- pymgarch-0.1.0/.github/workflows/python-publish.yml +86 -0
- pymgarch-0.1.0/.gitignore +30 -0
- pymgarch-0.1.0/.readthedocs.yaml +15 -0
- pymgarch-0.1.0/LICENSE +21 -0
- pymgarch-0.1.0/PKG-INFO +107 -0
- pymgarch-0.1.0/README.md +88 -0
- pymgarch-0.1.0/docs/api.md +40 -0
- pymgarch-0.1.0/docs/conf.py +26 -0
- pymgarch-0.1.0/docs/index.md +27 -0
- pymgarch-0.1.0/docs/inference.md +53 -0
- pymgarch-0.1.0/docs/models.md +62 -0
- pymgarch-0.1.0/docs/quickstart.md +61 -0
- pymgarch-0.1.0/docs/requirements.txt +7 -0
- pymgarch-0.1.0/pyproject.toml +63 -0
- pymgarch-0.1.0/scripts/make_fixtures.R +68 -0
- pymgarch-0.1.0/src/pymgarch/__init__.py +22 -0
- pymgarch-0.1.0/src/pymgarch/_kernels.py +151 -0
- pymgarch-0.1.0/src/pymgarch/_version.py +24 -0
- pymgarch-0.1.0/src/pymgarch/correlation.py +95 -0
- pymgarch-0.1.0/src/pymgarch/distributions.py +48 -0
- pymgarch-0.1.0/src/pymgarch/estimation.py +149 -0
- pymgarch-0.1.0/src/pymgarch/inference.py +200 -0
- pymgarch-0.1.0/src/pymgarch/marginals.py +246 -0
- pymgarch-0.1.0/src/pymgarch/models.py +251 -0
- pymgarch-0.1.0/src/pymgarch/results.py +353 -0
- pymgarch-0.1.0/tests/conftest.py +73 -0
- pymgarch-0.1.0/tests/fixtures/dji30ret_5.csv +1001 -0
- pymgarch-0.1.0/tests/fixtures/rmgarch_fixtures.json +1 -0
- pymgarch-0.1.0/tests/test_forecast_filter.py +78 -0
- pymgarch-0.1.0/tests/test_inference.py +25 -0
- pymgarch-0.1.0/tests/test_kernels.py +70 -0
- pymgarch-0.1.0/tests/test_marginals.py +59 -0
- pymgarch-0.1.0/tests/test_models.py +100 -0
- pymgarch-0.1.0/tests/test_numba.py +27 -0
- pymgarch-0.1.0/tests/test_replication.py +102 -0
- pymgarch-0.1.0/uv.lock +2170 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# .github/workflows/python-publish.yml
|
|
2
|
+
name: pymgarch CI/CD
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
tags: ["v*"]
|
|
8
|
+
pull_request:
|
|
9
|
+
branches: [main]
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
test:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
strategy:
|
|
18
|
+
matrix:
|
|
19
|
+
python-version: ["3.11", "3.13"]
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
with:
|
|
23
|
+
fetch-depth: 0 # full history for hatch-vcs
|
|
24
|
+
|
|
25
|
+
- name: Install uv
|
|
26
|
+
uses: astral-sh/setup-uv@v5
|
|
27
|
+
|
|
28
|
+
- name: Setup Python
|
|
29
|
+
uses: actions/setup-python@v5
|
|
30
|
+
with:
|
|
31
|
+
python-version: ${{ matrix.python-version }}
|
|
32
|
+
|
|
33
|
+
- name: Install dependencies
|
|
34
|
+
run: uv sync --dev --extra numba
|
|
35
|
+
|
|
36
|
+
- name: Run tests
|
|
37
|
+
run: uv run pytest tests/ -v --cov=src/pymgarch --cov-report=xml
|
|
38
|
+
|
|
39
|
+
- name: Upload coverage to Codecov
|
|
40
|
+
uses: codecov/codecov-action@v5
|
|
41
|
+
with:
|
|
42
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
43
|
+
files: coverage.xml
|
|
44
|
+
fail_ci_if_error: false
|
|
45
|
+
|
|
46
|
+
release:
|
|
47
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
48
|
+
needs: test
|
|
49
|
+
runs-on: ubuntu-latest
|
|
50
|
+
permissions:
|
|
51
|
+
contents: write
|
|
52
|
+
steps:
|
|
53
|
+
- uses: actions/checkout@v4
|
|
54
|
+
|
|
55
|
+
- name: Create GitHub Release
|
|
56
|
+
# --notes-from-tag publishes the annotated tag message as the release
|
|
57
|
+
# notes (--generate-notes only summarises PRs, which direct pushes lack);
|
|
58
|
+
# explicit --title keeps the release title to the bare tag name
|
|
59
|
+
run: gh release create "${{ github.ref_name }}" --notes-from-tag --title "${{ github.ref_name }}"
|
|
60
|
+
env:
|
|
61
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
62
|
+
|
|
63
|
+
deploy:
|
|
64
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
65
|
+
needs: [test, release]
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
environment: pypi
|
|
68
|
+
permissions:
|
|
69
|
+
id-token: write
|
|
70
|
+
steps:
|
|
71
|
+
- uses: actions/checkout@v4
|
|
72
|
+
with:
|
|
73
|
+
fetch-depth: 0 # full history for hatch-vcs
|
|
74
|
+
|
|
75
|
+
- name: Setup Python
|
|
76
|
+
uses: actions/setup-python@v5
|
|
77
|
+
with:
|
|
78
|
+
python-version: "3.13"
|
|
79
|
+
|
|
80
|
+
- name: Build
|
|
81
|
+
run: |
|
|
82
|
+
pip install build hatch-vcs
|
|
83
|
+
python -m build
|
|
84
|
+
|
|
85
|
+
- name: Publish to PyPI
|
|
86
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.venv/
|
|
8
|
+
.python-version
|
|
9
|
+
|
|
10
|
+
# Generated version file
|
|
11
|
+
src/pymgarch/_version.py
|
|
12
|
+
|
|
13
|
+
# Tooling caches
|
|
14
|
+
.pytest_cache/
|
|
15
|
+
.ruff_cache/
|
|
16
|
+
.mypy_cache/
|
|
17
|
+
.hypothesis/
|
|
18
|
+
.coverage
|
|
19
|
+
coverage.xml
|
|
20
|
+
|
|
21
|
+
# Docs build
|
|
22
|
+
docs/_build/
|
|
23
|
+
|
|
24
|
+
# IDE
|
|
25
|
+
.idea/
|
|
26
|
+
.vscode/
|
|
27
|
+
|
|
28
|
+
# Local project settings
|
|
29
|
+
.claude/settings.local.json
|
|
30
|
+
uv.lock.orig
|
pymgarch-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Marwin Steiner
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pymgarch-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pymgarch
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Multivariate GARCH for Python: DCC, ADCC, CCC on top of arch marginals, validated against rmgarch/tsmarch.
|
|
5
|
+
Project-URL: Documentation, https://pymgarch.readthedocs.io
|
|
6
|
+
Project-URL: Repository, https://github.com/marwinsteiner/pymgarch
|
|
7
|
+
Project-URL: Issues, https://github.com/marwinsteiner/pymgarch/issues
|
|
8
|
+
Author: Marwin Steiner
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Requires-Dist: arch>=7.0
|
|
13
|
+
Requires-Dist: numpy>=1.26
|
|
14
|
+
Requires-Dist: pandas>=2.0
|
|
15
|
+
Requires-Dist: scipy>=1.11
|
|
16
|
+
Provides-Extra: numba
|
|
17
|
+
Requires-Dist: numba>=0.59; extra == 'numba'
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# pymgarch
|
|
21
|
+
|
|
22
|
+
[](https://github.com/marwinsteiner/pymgarch/actions/workflows/python-publish.yml)
|
|
23
|
+
[](https://pypi.org/project/pymgarch/)
|
|
24
|
+
[](https://pypi.org/project/pymgarch/)
|
|
25
|
+
[](https://pepy.tech/project/pymgarch)
|
|
26
|
+
[](https://codecov.io/gh/marwinsteiner/pymgarch)
|
|
27
|
+
[](https://pymgarch.readthedocs.io)
|
|
28
|
+
[](LICENSE)
|
|
29
|
+
|
|
30
|
+
Multivariate GARCH for Python: DCC, ADCC, and CCC correlation dynamics built
|
|
31
|
+
on top of [arch](https://github.com/bashtage/arch) univariate marginals,
|
|
32
|
+
validated against R's rmgarch/tsmarch.
|
|
33
|
+
|
|
34
|
+
## Why
|
|
35
|
+
|
|
36
|
+
Python has no maintained general-purpose multivariate GARCH framework. The
|
|
37
|
+
existing packages cover Gaussian DCC(1,1) at most, while R users have had
|
|
38
|
+
DCC, ADCC, GO-GARCH and copula-GARCH in rmgarch (now tsmarch) for a decade.
|
|
39
|
+
pymgarch closes that gap incrementally, starting with the correlation layer:
|
|
40
|
+
|
|
41
|
+
- stage 1 (univariate volatility) is delegated to `arch`, the ecosystem's
|
|
42
|
+
dominant, battle-tested GARCH package;
|
|
43
|
+
- stage 2 (correlation dynamics) is what this library implements, with
|
|
44
|
+
correct two-stage Engle-Sheppard standard errors and replication tests
|
|
45
|
+
against rmgarch's fitted parameters and likelihoods.
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
pip install pymgarch # or: pip install pymgarch[numba]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The optional `numba` extra JIT-compiles the correlation recursions; without
|
|
54
|
+
it everything runs in pure NumPy.
|
|
55
|
+
|
|
56
|
+
## Quickstart
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import pymgarch as mg
|
|
60
|
+
|
|
61
|
+
# returns: (T, N) DataFrame, percent scale recommended
|
|
62
|
+
res = mg.DCC(dist="t").fit(returns)
|
|
63
|
+
print(res.summary())
|
|
64
|
+
|
|
65
|
+
res.conditional_correlations # (T, N, N)
|
|
66
|
+
res.conditional_covariances # (T, N, N)
|
|
67
|
+
|
|
68
|
+
fc = res.forecast(horizon=10) # analytic
|
|
69
|
+
fc = res.forecast(horizon=10, method="simulation", n_paths=2000)
|
|
70
|
+
flt = res.filter(new_returns) # fixed params, new data
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Marginals default to constant-mean GARCH(1,1). Customize per-column via a
|
|
74
|
+
spec, or bring your own fitted arch results:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
spec = mg.UnivariateSpec(vol="GARCH", p=1, o=1, q=1, dist="t") # GJR-t
|
|
78
|
+
res = mg.ADCC().fit(returns, marginals=spec)
|
|
79
|
+
|
|
80
|
+
from arch import arch_model
|
|
81
|
+
fitted = [arch_model(returns[c], rescale=False).fit(disp="off") for c in returns]
|
|
82
|
+
res = mg.DCC().fit(returns, marginals=fitted)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Models (v0.1)
|
|
86
|
+
|
|
87
|
+
| Model | Distribution | Estimation |
|
|
88
|
+
| ----- | ------------ | ---------- |
|
|
89
|
+
| CCC (Bollerslev 1990) | Gaussian | closed form given marginals |
|
|
90
|
+
| DCC(1,1) (Engle 2002) | Gaussian, Student-t | two-stage QML, correlation targeting |
|
|
91
|
+
| ADCC (Cappiello-Engle-Sheppard 2006) | Gaussian, Student-t | two-stage QML, PSD-constrained targeting |
|
|
92
|
+
|
|
93
|
+
Standard errors are Engle-Sheppard (2001) two-stage sandwich estimates: the
|
|
94
|
+
marginal and correlation scores are stacked so stage-2 uncertainty reflects
|
|
95
|
+
stage-1 estimation error. Correlation targets are held fixed (same
|
|
96
|
+
approximation rmgarch makes). If the stacked system is singular the library
|
|
97
|
+
falls back to a stage-2-only sandwich and says so in `summary()`.
|
|
98
|
+
|
|
99
|
+
## Roadmap
|
|
100
|
+
|
|
101
|
+
- v0.2: GO-GARCH via ICA
|
|
102
|
+
- v0.3: copula-GARCH (Gaussian and Student-t copulas)
|
|
103
|
+
- v0.4: composite likelihood for large cross-sections, scalar/diagonal BEKK
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
MIT
|
pymgarch-0.1.0/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# pymgarch
|
|
2
|
+
|
|
3
|
+
[](https://github.com/marwinsteiner/pymgarch/actions/workflows/python-publish.yml)
|
|
4
|
+
[](https://pypi.org/project/pymgarch/)
|
|
5
|
+
[](https://pypi.org/project/pymgarch/)
|
|
6
|
+
[](https://pepy.tech/project/pymgarch)
|
|
7
|
+
[](https://codecov.io/gh/marwinsteiner/pymgarch)
|
|
8
|
+
[](https://pymgarch.readthedocs.io)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
|
|
11
|
+
Multivariate GARCH for Python: DCC, ADCC, and CCC correlation dynamics built
|
|
12
|
+
on top of [arch](https://github.com/bashtage/arch) univariate marginals,
|
|
13
|
+
validated against R's rmgarch/tsmarch.
|
|
14
|
+
|
|
15
|
+
## Why
|
|
16
|
+
|
|
17
|
+
Python has no maintained general-purpose multivariate GARCH framework. The
|
|
18
|
+
existing packages cover Gaussian DCC(1,1) at most, while R users have had
|
|
19
|
+
DCC, ADCC, GO-GARCH and copula-GARCH in rmgarch (now tsmarch) for a decade.
|
|
20
|
+
pymgarch closes that gap incrementally, starting with the correlation layer:
|
|
21
|
+
|
|
22
|
+
- stage 1 (univariate volatility) is delegated to `arch`, the ecosystem's
|
|
23
|
+
dominant, battle-tested GARCH package;
|
|
24
|
+
- stage 2 (correlation dynamics) is what this library implements, with
|
|
25
|
+
correct two-stage Engle-Sheppard standard errors and replication tests
|
|
26
|
+
against rmgarch's fitted parameters and likelihoods.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
pip install pymgarch # or: pip install pymgarch[numba]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The optional `numba` extra JIT-compiles the correlation recursions; without
|
|
35
|
+
it everything runs in pure NumPy.
|
|
36
|
+
|
|
37
|
+
## Quickstart
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import pymgarch as mg
|
|
41
|
+
|
|
42
|
+
# returns: (T, N) DataFrame, percent scale recommended
|
|
43
|
+
res = mg.DCC(dist="t").fit(returns)
|
|
44
|
+
print(res.summary())
|
|
45
|
+
|
|
46
|
+
res.conditional_correlations # (T, N, N)
|
|
47
|
+
res.conditional_covariances # (T, N, N)
|
|
48
|
+
|
|
49
|
+
fc = res.forecast(horizon=10) # analytic
|
|
50
|
+
fc = res.forecast(horizon=10, method="simulation", n_paths=2000)
|
|
51
|
+
flt = res.filter(new_returns) # fixed params, new data
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Marginals default to constant-mean GARCH(1,1). Customize per-column via a
|
|
55
|
+
spec, or bring your own fitted arch results:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
spec = mg.UnivariateSpec(vol="GARCH", p=1, o=1, q=1, dist="t") # GJR-t
|
|
59
|
+
res = mg.ADCC().fit(returns, marginals=spec)
|
|
60
|
+
|
|
61
|
+
from arch import arch_model
|
|
62
|
+
fitted = [arch_model(returns[c], rescale=False).fit(disp="off") for c in returns]
|
|
63
|
+
res = mg.DCC().fit(returns, marginals=fitted)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Models (v0.1)
|
|
67
|
+
|
|
68
|
+
| Model | Distribution | Estimation |
|
|
69
|
+
| ----- | ------------ | ---------- |
|
|
70
|
+
| CCC (Bollerslev 1990) | Gaussian | closed form given marginals |
|
|
71
|
+
| DCC(1,1) (Engle 2002) | Gaussian, Student-t | two-stage QML, correlation targeting |
|
|
72
|
+
| ADCC (Cappiello-Engle-Sheppard 2006) | Gaussian, Student-t | two-stage QML, PSD-constrained targeting |
|
|
73
|
+
|
|
74
|
+
Standard errors are Engle-Sheppard (2001) two-stage sandwich estimates: the
|
|
75
|
+
marginal and correlation scores are stacked so stage-2 uncertainty reflects
|
|
76
|
+
stage-1 estimation error. Correlation targets are held fixed (same
|
|
77
|
+
approximation rmgarch makes). If the stacked system is singular the library
|
|
78
|
+
falls back to a stage-2-only sandwich and says so in `summary()`.
|
|
79
|
+
|
|
80
|
+
## Roadmap
|
|
81
|
+
|
|
82
|
+
- v0.2: GO-GARCH via ICA
|
|
83
|
+
- v0.3: copula-GARCH (Gaussian and Student-t copulas)
|
|
84
|
+
- v0.4: composite likelihood for large cross-sections, scalar/diagonal BEKK
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# API reference
|
|
2
|
+
|
|
3
|
+
## Models
|
|
4
|
+
|
|
5
|
+
```{eval-rst}
|
|
6
|
+
.. autoclass:: pymgarch.DCC
|
|
7
|
+
:members:
|
|
8
|
+
|
|
9
|
+
.. autoclass:: pymgarch.ADCC
|
|
10
|
+
:members:
|
|
11
|
+
|
|
12
|
+
.. autoclass:: pymgarch.CCC
|
|
13
|
+
:members:
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Marginals
|
|
17
|
+
|
|
18
|
+
```{eval-rst}
|
|
19
|
+
.. autoclass:: pymgarch.UnivariateSpec
|
|
20
|
+
:members:
|
|
21
|
+
|
|
22
|
+
.. autoclass:: pymgarch.MarginalSet
|
|
23
|
+
:members:
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Results
|
|
27
|
+
|
|
28
|
+
```{eval-rst}
|
|
29
|
+
.. autoclass:: pymgarch.MGARCHResult
|
|
30
|
+
:members:
|
|
31
|
+
|
|
32
|
+
.. autoclass:: pymgarch.MGARCHForecast
|
|
33
|
+
:members:
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Simulation
|
|
37
|
+
|
|
38
|
+
```{eval-rst}
|
|
39
|
+
.. autofunction:: pymgarch.simulate_dcc
|
|
40
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
project = "pymgarch"
|
|
2
|
+
author = "Marwin Steiner"
|
|
3
|
+
copyright = "2026, Marwin Steiner"
|
|
4
|
+
|
|
5
|
+
extensions = [
|
|
6
|
+
"myst_parser",
|
|
7
|
+
"sphinx.ext.autodoc",
|
|
8
|
+
"sphinx.ext.napoleon",
|
|
9
|
+
"sphinx.ext.intersphinx",
|
|
10
|
+
"sphinx.ext.viewcode",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
myst_enable_extensions = ["dollarmath", "amsmath"]
|
|
14
|
+
|
|
15
|
+
intersphinx_mapping = {
|
|
16
|
+
"python": ("https://docs.python.org/3", None),
|
|
17
|
+
"numpy": ("https://numpy.org/doc/stable/", None),
|
|
18
|
+
"pandas": ("https://pandas.pydata.org/docs/", None),
|
|
19
|
+
"scipy": ("https://docs.scipy.org/doc/scipy/", None),
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
html_theme = "furo"
|
|
23
|
+
html_title = "pymgarch"
|
|
24
|
+
|
|
25
|
+
source_suffix = {".rst": "restructuredtext", ".md": "markdown"}
|
|
26
|
+
exclude_patterns = ["_build"]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# pymgarch
|
|
2
|
+
|
|
3
|
+
Multivariate GARCH for Python: DCC, ADCC, and CCC correlation dynamics on top
|
|
4
|
+
of `arch` univariate marginals, validated against R's rmgarch.
|
|
5
|
+
|
|
6
|
+
Python has had no maintained general-purpose multivariate GARCH framework:
|
|
7
|
+
the existing packages stop at Gaussian DCC(1,1), while R users have had a
|
|
8
|
+
full model stack in rmgarch (now tsmarch) for a decade. pymgarch closes that
|
|
9
|
+
gap incrementally. Stage 1 (univariate volatility) is delegated to `arch`;
|
|
10
|
+
stage 2 (correlation dynamics) is what this library implements, with
|
|
11
|
+
Engle-Sheppard two-stage standard errors and replication tests against
|
|
12
|
+
rmgarch's fitted parameters and likelihoods.
|
|
13
|
+
|
|
14
|
+
```{toctree}
|
|
15
|
+
:maxdepth: 2
|
|
16
|
+
|
|
17
|
+
quickstart
|
|
18
|
+
models
|
|
19
|
+
inference
|
|
20
|
+
api
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
pip install pymgarch # or: pip install pymgarch[numba]
|
|
27
|
+
```
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Inference
|
|
2
|
+
|
|
3
|
+
## Two-stage standard errors
|
|
4
|
+
|
|
5
|
+
Two-stage QML is consistent but the stage-2 information matrix alone
|
|
6
|
+
understates uncertainty: the correlation parameters are estimated on
|
|
7
|
+
residuals that were themselves standardized by estimated marginals. pymgarch
|
|
8
|
+
implements the Engle and Sheppard (2001) correction by stacking the
|
|
9
|
+
per-observation scores of every marginal likelihood and the correlation
|
|
10
|
+
likelihood into one M-estimator moment vector
|
|
11
|
+
$g_t(\theta)$, $\theta = (\phi_1, \dots, \phi_N, \psi)$, and computing
|
|
12
|
+
|
|
13
|
+
$$
|
|
14
|
+
\widehat{\mathrm{Avar}}(\hat\theta) = \hat A^{-1} \hat B \hat A^{-\top}/T,
|
|
15
|
+
\qquad
|
|
16
|
+
\hat A = \frac{1}{T}\sum_t \frac{\partial g_t}{\partial \theta'},
|
|
17
|
+
\qquad
|
|
18
|
+
\hat B = \frac{1}{T}\sum_t g_t g_t'.
|
|
19
|
+
$$
|
|
20
|
+
|
|
21
|
+
$\hat A$ is block lower-triangular because marginal scores do not depend on
|
|
22
|
+
$\psi$. All derivatives are central finite differences; marginal likelihoods
|
|
23
|
+
are re-evaluated through arch's own variance recursions with the backcast and
|
|
24
|
+
variance bounds arch used during fitting held fixed, so the perturbed
|
|
25
|
+
evaluations are consistent with the objective arch actually maximized.
|
|
26
|
+
|
|
27
|
+
`summary()` labels the estimates `two-stage-robust`. If the stacked bread
|
|
28
|
+
matrix is numerically singular, the library falls back to a stage-2-only
|
|
29
|
+
sandwich (labelled `stage2-robust`) and warns; those standard errors ignore
|
|
30
|
+
marginal estimation error.
|
|
31
|
+
|
|
32
|
+
## Known approximations
|
|
33
|
+
|
|
34
|
+
- Correlation targets $\bar S$ and $\bar N$ are held fixed at their point
|
|
35
|
+
estimates, so targeting uncertainty is ignored. rmgarch makes the same
|
|
36
|
+
approximation.
|
|
37
|
+
- Set `compute_se=False` on `fit()` to skip the sandwich entirely (useful in
|
|
38
|
+
simulation studies where only point estimates matter).
|
|
39
|
+
|
|
40
|
+
## Replication against rmgarch
|
|
41
|
+
|
|
42
|
+
`scripts/make_fixtures.R` fits rmgarch on a pinned dataset (percent returns
|
|
43
|
+
of five dji30ret constituents) and exports parameters, likelihoods, sigma and
|
|
44
|
+
residual paths, correlation targets, and one-step forecasts to JSON. The
|
|
45
|
+
test suite then:
|
|
46
|
+
|
|
47
|
+
1. evaluates pymgarch's likelihood at R's fitted parameters on R's own
|
|
48
|
+
volatility paths and correlation targets, isolating the correlation math
|
|
49
|
+
from optimizer and univariate-initialization differences; and
|
|
50
|
+
2. runs the full pipeline on the same CSV and requires parameter agreement
|
|
51
|
+
and a log-likelihood no worse than R's optimum.
|
|
52
|
+
|
|
53
|
+
The tests skip automatically when the fixture file is absent.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Models
|
|
2
|
+
|
|
3
|
+
All stage-2 models operate on standardized residuals $\varepsilon_t = D_t^{-1}
|
|
4
|
+
(r_t - \mu_t)$, where $D_t = \mathrm{diag}(\sigma_{1t}, \dots, \sigma_{Nt})$
|
|
5
|
+
comes from the fitted arch marginals. The conditional covariance is
|
|
6
|
+
$H_t = D_t R_t D_t$.
|
|
7
|
+
|
|
8
|
+
## CCC (Bollerslev 1990)
|
|
9
|
+
|
|
10
|
+
Constant correlation $R = \mathrm{cov2cor}(E'E/T)$. No stage-2 parameters;
|
|
11
|
+
closed form given the marginals. Gaussian likelihood only in v0.1.
|
|
12
|
+
|
|
13
|
+
## DCC(1,1) (Engle 2002)
|
|
14
|
+
|
|
15
|
+
With correlation targeting $\bar S = \mathrm{cov2cor}(E'E/T)$:
|
|
16
|
+
|
|
17
|
+
$$
|
|
18
|
+
Q_t = (1 - a - b)\bar S + a\,\varepsilon_{t-1}\varepsilon_{t-1}' + b\,Q_{t-1},
|
|
19
|
+
\qquad
|
|
20
|
+
R_t = \mathrm{diag}(Q_t)^{-1/2} Q_t \,\mathrm{diag}(Q_t)^{-1/2},
|
|
21
|
+
$$
|
|
22
|
+
|
|
23
|
+
with $a, b \ge 0$, $a + b < 1$, $Q_1 = \bar S$. Estimated by two-stage QML
|
|
24
|
+
(SLSQP, multiple starts). Distributions: Gaussian and covariance-standardized
|
|
25
|
+
multivariate Student-t (degrees of freedom estimated jointly with $a, b$).
|
|
26
|
+
|
|
27
|
+
## ADCC (Cappiello, Engle and Sheppard 2006)
|
|
28
|
+
|
|
29
|
+
Scalar asymmetric DCC with $n_t = \min(\varepsilon_t, 0)$ elementwise and
|
|
30
|
+
$\bar N = T^{-1}\sum_t n_t n_t'$:
|
|
31
|
+
|
|
32
|
+
$$
|
|
33
|
+
Q_t = \left[(1-a-b)\bar S - g\bar N\right]
|
|
34
|
+
+ a\,\varepsilon_{t-1}\varepsilon_{t-1}'
|
|
35
|
+
+ g\,n_{t-1}n_{t-1}'
|
|
36
|
+
+ b\,Q_{t-1}.
|
|
37
|
+
$$
|
|
38
|
+
|
|
39
|
+
The intercept is positive semi-definite iff $a + b + \delta g \le 1$ with
|
|
40
|
+
$\delta = \lambda_{\max}(\bar S^{-1/2} \bar N \bar S^{-1/2})$, which is a
|
|
41
|
+
linear constraint in $(a, b, g)$ and is imposed exactly during estimation.
|
|
42
|
+
|
|
43
|
+
## Forecasting
|
|
44
|
+
|
|
45
|
+
Marginal variance forecasts are delegated to arch. One-step correlations are
|
|
46
|
+
deterministic. For $h \ge 2$, DCC uses the Engle-Sheppard approximation
|
|
47
|
+
|
|
48
|
+
$$
|
|
49
|
+
E[R_{T+h}] \approx \left(1 - (a+b)^{h-1}\right)\bar S + (a+b)^{h-1} R_{T+1},
|
|
50
|
+
$$
|
|
51
|
+
|
|
52
|
+
and every model supports Monte Carlo forecasts (`method="simulation"`) that
|
|
53
|
+
propagate the exact recursions; ADCC requires them. The analytic covariance
|
|
54
|
+
assembly $\hat H = \hat D \hat R \hat D$ ignores a Jensen gap that the
|
|
55
|
+
simulation method does not.
|
|
56
|
+
|
|
57
|
+
## Reported likelihood
|
|
58
|
+
|
|
59
|
+
`loglikelihood` is the joint likelihood of the returns under the stage-2
|
|
60
|
+
distribution (the same quantity rmgarch's `likelihood()` reports): the
|
|
61
|
+
multivariate density of $\varepsilon_t$ at $R_t$ plus the Jacobian
|
|
62
|
+
$-\sum_i \log \sigma_{it}$, summed over $t$.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Quickstart
|
|
2
|
+
|
|
3
|
+
Fit a Gaussian DCC(1,1) with default constant-mean GARCH(1,1) marginals:
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
import pymgarch as mg
|
|
7
|
+
|
|
8
|
+
res = mg.DCC().fit(returns) # (T, N) DataFrame, percent scale
|
|
9
|
+
print(res.summary())
|
|
10
|
+
|
|
11
|
+
res.params # {"alpha": ..., "beta": ...}
|
|
12
|
+
res.std_errors # two-stage robust standard errors
|
|
13
|
+
res.conditional_correlations # (T, N, N)
|
|
14
|
+
res.conditional_covariances # (T, N, N)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Distributions and asymmetry
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
mg.DCC(dist="t").fit(returns) # multivariate Student-t stage 2
|
|
21
|
+
mg.ADCC().fit(returns) # asymmetric (Cappiello-Engle-Sheppard)
|
|
22
|
+
mg.CCC().fit(returns) # constant-correlation baseline
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Custom marginals
|
|
26
|
+
|
|
27
|
+
Apply one spec to every column, or bring fitted arch results:
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
spec = mg.UnivariateSpec(vol="GARCH", p=1, o=1, q=1, dist="t") # GJR-t
|
|
31
|
+
res = mg.DCC().fit(returns, marginals=spec)
|
|
32
|
+
|
|
33
|
+
from arch import arch_model
|
|
34
|
+
fitted = [
|
|
35
|
+
arch_model(returns[c], rescale=False).fit(disp="off")
|
|
36
|
+
for c in returns.columns
|
|
37
|
+
]
|
|
38
|
+
res = mg.DCC().fit(returns, marginals=fitted)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Forecasting and filtering
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
fc = res.forecast(horizon=10) # analytic
|
|
45
|
+
fc = res.forecast(horizon=10, method="simulation", n_paths=2000)
|
|
46
|
+
fc.variances # (h, N)
|
|
47
|
+
fc.correlations # (h, N, N)
|
|
48
|
+
fc.covariances # (h, N, N)
|
|
49
|
+
|
|
50
|
+
flt = res.filter(new_returns) # apply fitted params to new data
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
ADCC has no standard analytic multi-step correlation forecast; use
|
|
54
|
+
`method="simulation"` there.
|
|
55
|
+
|
|
56
|
+
## Scaling note
|
|
57
|
+
|
|
58
|
+
Like `arch`, the optimizers behave best when returns are on a percent scale
|
|
59
|
+
(roughly unit variance). pymgarch fits marginals with `rescale=False` so
|
|
60
|
+
parameters stay comparable with R fits on the same series; multiply decimal
|
|
61
|
+
returns by 100 before fitting.
|