1
1
from arch .compat .pandas import MONTH_END
2
2
3
3
from io import StringIO
4
+ import itertools
4
5
from itertools import product
5
6
from string import ascii_lowercase
6
7
import struct
7
8
import sys
8
9
import types
9
10
import warnings
10
11
12
+ import matplotlib .pyplot as plt
11
13
import numpy as np
12
14
from numpy .random import RandomState
13
15
from numpy .testing import (
26
28
27
29
from arch .data import sp500
28
30
from arch .typing import Literal
29
- from arch .univariate .base import ARCHModelForecast , ARCHModelResult , _align_forecast
31
+ from arch .univariate .base import (
32
+ ARCHModel ,
33
+ ARCHModelForecast ,
34
+ ARCHModelResult ,
35
+ _align_forecast ,
36
+ )
30
37
from arch .univariate .distribution import (
31
38
GeneralizedError ,
32
39
Normal ,
46
53
FixedVariance ,
47
54
MIDASHyperbolic ,
48
55
RiskMetrics2006 ,
56
+ VolatilityProcess ,
49
57
)
50
58
from arch .utility .exceptions import ConvergenceWarning , DataScaleWarning
51
59
73
81
DISPLAY : Literal ["off" , "final" ] = "off"
74
82
UPDATE_FREQ = 0 if DISPLAY == "off" else 3
75
83
SP500 = 100 * sp500 .load ()["Adj Close" ].pct_change ().dropna ()
84
+ rs = np .random .RandomState (20241029 )
85
+ X = SP500 * 0.01 + SP500 .std () * rs .standard_normal (SP500 .shape )
76
86
77
87
78
88
@pytest .fixture (scope = "module" , params = [True , False ])
@@ -83,6 +93,73 @@ def simulated_data(request):
83
93
return np .asarray (sim_data .data ) if request .param else sim_data .data
84
94
85
95
96
+ simple_mean_models = [
97
+ ARX (SP500 , lags = 1 ),
98
+ HARX (SP500 , lags = [1 , 5 ]),
99
+ ConstantMean (SP500 ),
100
+ ZeroMean (SP500 ),
101
+ ]
102
+
103
+ mean_models = [
104
+ ARX (SP500 , x = X , lags = 1 ),
105
+ HARX (SP500 , x = X , lags = [1 , 5 ]),
106
+ LS (SP500 , X ),
107
+ ] + simple_mean_models
108
+
109
+ analytic_volatility_processes = [
110
+ ARCH (3 ),
111
+ FIGARCH (1 , 1 ),
112
+ GARCH (1 , 1 , 1 ),
113
+ HARCH ([1 , 5 , 22 ]),
114
+ ConstantVariance (),
115
+ EWMAVariance (0.94 ),
116
+ FixedVariance (np .full_like (SP500 , SP500 .var ())),
117
+ MIDASHyperbolic (),
118
+ RiskMetrics2006 (),
119
+ ]
120
+
121
+ other_volatility_processes = [
122
+ APARCH (1 , 1 , 1 , 1.5 ),
123
+ EGARCH (1 , 1 , 1 ),
124
+ ]
125
+
126
+ volatility_processes = analytic_volatility_processes + other_volatility_processes
127
+
128
+
129
+ @pytest .fixture (
130
+ scope = "module" ,
131
+ params = list (itertools .product (simple_mean_models , analytic_volatility_processes )),
132
+ ids = [
133
+ f"{ a .__class__ .__name__ } -{ b } "
134
+ for a , b in itertools .product (simple_mean_models , analytic_volatility_processes )
135
+ ],
136
+ )
137
+ def forecastable_model (request ):
138
+ mod : ARCHModel
139
+ vol : VolatilityProcess
140
+ mod , vol = request .param
141
+ mod .volatility = vol
142
+ res = mod .fit ()
143
+ return res , mod .fix (res .params )
144
+
145
+
146
+ @pytest .fixture (
147
+ scope = "module" ,
148
+ params = list (itertools .product (mean_models , volatility_processes )),
149
+ ids = [
150
+ f"{ a .__class__ .__name__ } -{ b } "
151
+ for a , b in itertools .product (mean_models , volatility_processes )
152
+ ],
153
+ )
154
+ def fit_fixed_models (request ):
155
+ mod : ARCHModel
156
+ vol : VolatilityProcess
157
+ mod , vol = request .param
158
+ mod .volatility = vol
159
+ res = mod .fit ()
160
+ return res , mod .fix (res .params )
161
+
162
+
86
163
class TestMeanModel :
87
164
@classmethod
88
165
def setup_class (cls ):
@@ -1370,3 +1447,49 @@ def test_non_contiguous_input(use_numpy):
1370
1447
mod = arch_model (y , mean = "Zero" )
1371
1448
res = mod .fit ()
1372
1449
assert res .params .shape [0 ] == 3
1450
+
1451
+
1452
+ def test_fixed_equivalence (fit_fixed_models ):
1453
+ res , res_fixed = fit_fixed_models
1454
+
1455
+ assert_allclose (res .aic , res_fixed .aic )
1456
+ assert_allclose (res .bic , res_fixed .bic )
1457
+ assert_allclose (res .loglikelihood , res_fixed .loglikelihood )
1458
+ assert res .nobs == res_fixed .nobs
1459
+ assert res .num_params == res_fixed .num_params
1460
+ assert_allclose (res .params , res_fixed .params )
1461
+ assert_allclose (res .conditional_volatility , res_fixed .conditional_volatility )
1462
+ assert_allclose (res .std_resid , res_fixed .std_resid )
1463
+ assert_allclose (res .resid , res_fixed .resid )
1464
+ assert_allclose (res .arch_lm_test (5 ).stat , res_fixed .arch_lm_test (5 ).stat )
1465
+ assert res .model .__class__ is res_fixed .model .__class__
1466
+ assert res .model .volatility .__class__ is res_fixed .model .volatility .__class__
1467
+ fig = res .plot ()
1468
+ fixed_fig = res_fixed .plot ()
1469
+ assert isinstance (fig , type (fixed_fig ))
1470
+ assert isinstance (res .summary (), type (res_fixed .summary ()))
1471
+ if res .num_params > 0 :
1472
+ assert "std err" in str (res .summary ())
1473
+ assert "std err" not in str (res_fixed .summary ())
1474
+ plt .close ("all" )
1475
+
1476
+
1477
+ @pytest .mark .parametrize ("simulations" , [1 , 100 ])
1478
+ def test_fixed_equivalence_forecastable (forecastable_model , simulations ):
1479
+ res , res_fixed = forecastable_model
1480
+ f1 = res .forecast (horizon = 5 )
1481
+ f2 = res_fixed .forecast (horizon = 5 )
1482
+ assert isinstance (f1 , type (f2 ))
1483
+ assert_allclose (f1 .mean , f2 .mean )
1484
+ assert_allclose (f1 .variance , f2 .variance )
1485
+ fig1 = res .hedgehog_plot (start = SP500 .shape [0 ] - 25 )
1486
+ fig2 = res_fixed .hedgehog_plot (start = SP500 .shape [0 ] - 25 )
1487
+ assert isinstance (fig1 , type (fig2 ))
1488
+ plt .close ("all" )
1489
+
1490
+ f1 = res .forecast (horizon = 5 , method = "simulation" , simulations = simulations )
1491
+ f2 = res_fixed .forecast (horizon = 5 , method = "simulation" , simulations = simulations )
1492
+ assert isinstance (f1 , type (f2 ))
1493
+ f1 = res .forecast (horizon = 5 , method = "bootstrap" , simulations = simulations )
1494
+ f2 = res_fixed .forecast (horizon = 5 , method = "bootstrap" , simulations = simulations )
1495
+ assert isinstance (f1 , type (f2 ))
0 commit comments