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
26
27
27
28
from arch .data import sp500
28
29
from arch .typing import Literal
29
- from arch .univariate .base import ARCHModelForecast , ARCHModelResult , _align_forecast
30
+ from arch .univariate .base import (
31
+ ARCHModel ,
32
+ ARCHModelForecast ,
33
+ ARCHModelResult ,
34
+ _align_forecast ,
35
+ )
30
36
from arch .univariate .distribution import (
31
37
GeneralizedError ,
32
38
Normal ,
46
52
FixedVariance ,
47
53
MIDASHyperbolic ,
48
54
RiskMetrics2006 ,
55
+ VolatilityProcess ,
49
56
)
50
57
from arch .utility .exceptions import ConvergenceWarning , DataScaleWarning
51
58
73
80
DISPLAY : Literal ["off" , "final" ] = "off"
74
81
UPDATE_FREQ = 0 if DISPLAY == "off" else 3
75
82
SP500 = 100 * sp500 .load ()["Adj Close" ].pct_change ().dropna ()
83
+ rs = np .random .RandomState (20241029 )
84
+ X = SP500 * 0.01 + SP500 .std () * rs .standard_normal (SP500 .shape )
85
+
86
+
87
+ def close_plots ():
88
+ import matplotlib .pyplot as plt
89
+
90
+ plt .close ("all" )
76
91
77
92
78
93
@pytest .fixture (scope = "module" , params = [True , False ])
@@ -83,6 +98,73 @@ def simulated_data(request):
83
98
return np .asarray (sim_data .data ) if request .param else sim_data .data
84
99
85
100
101
+ simple_mean_models = [
102
+ ARX (SP500 , lags = 1 ),
103
+ HARX (SP500 , lags = [1 , 5 ]),
104
+ ConstantMean (SP500 ),
105
+ ZeroMean (SP500 ),
106
+ ]
107
+
108
+ mean_models = [
109
+ ARX (SP500 , x = X , lags = 1 ),
110
+ HARX (SP500 , x = X , lags = [1 , 5 ]),
111
+ LS (SP500 , X ),
112
+ ] + simple_mean_models
113
+
114
+ analytic_volatility_processes = [
115
+ ARCH (3 ),
116
+ FIGARCH (1 , 1 ),
117
+ GARCH (1 , 1 , 1 ),
118
+ HARCH ([1 , 5 , 22 ]),
119
+ ConstantVariance (),
120
+ EWMAVariance (0.94 ),
121
+ FixedVariance (np .full_like (SP500 , SP500 .var ())),
122
+ MIDASHyperbolic (),
123
+ RiskMetrics2006 (),
124
+ ]
125
+
126
+ other_volatility_processes = [
127
+ APARCH (1 , 1 , 1 , 1.5 ),
128
+ EGARCH (1 , 1 , 1 ),
129
+ ]
130
+
131
+ volatility_processes = analytic_volatility_processes + other_volatility_processes
132
+
133
+
134
+ @pytest .fixture (
135
+ scope = "module" ,
136
+ params = list (itertools .product (simple_mean_models , analytic_volatility_processes )),
137
+ ids = [
138
+ f"{ a .__class__ .__name__ } -{ b } "
139
+ for a , b in itertools .product (simple_mean_models , analytic_volatility_processes )
140
+ ],
141
+ )
142
+ def forecastable_model (request ):
143
+ mod : ARCHModel
144
+ vol : VolatilityProcess
145
+ mod , vol = request .param
146
+ mod .volatility = vol
147
+ res = mod .fit ()
148
+ return res , mod .fix (res .params )
149
+
150
+
151
+ @pytest .fixture (
152
+ scope = "module" ,
153
+ params = list (itertools .product (mean_models , volatility_processes )),
154
+ ids = [
155
+ f"{ a .__class__ .__name__ } -{ b } "
156
+ for a , b in itertools .product (mean_models , volatility_processes )
157
+ ],
158
+ )
159
+ def fit_fixed_models (request ):
160
+ mod : ARCHModel
161
+ vol : VolatilityProcess
162
+ mod , vol = request .param
163
+ mod .volatility = vol
164
+ res = mod .fit ()
165
+ return res , mod .fix (res .params )
166
+
167
+
86
168
class TestMeanModel :
87
169
@classmethod
88
170
def setup_class (cls ):
@@ -480,9 +562,7 @@ def test_ar_plot(self):
480
562
with pytest .raises (ValueError ):
481
563
res .plot (annualize = "unknown" )
482
564
483
- import matplotlib .pyplot as plt
484
-
485
- plt .close ("all" )
565
+ close_plots ()
486
566
487
567
res .plot (scale = 360 )
488
568
res .hedgehog_plot (start = 500 )
@@ -491,7 +571,7 @@ def test_ar_plot(self):
491
571
res .hedgehog_plot (start = 500 , method = "simulation" , simulations = 100 )
492
572
res .hedgehog_plot (plot_type = "volatility" , method = "bootstrap" )
493
573
494
- plt . close ( "all" )
574
+ close_plots ( )
495
575
496
576
def test_arch_arx (self ):
497
577
self .rng .seed (12345 )
@@ -1370,3 +1450,61 @@ def test_non_contiguous_input(use_numpy):
1370
1450
mod = arch_model (y , mean = "Zero" )
1371
1451
res = mod .fit ()
1372
1452
assert res .params .shape [0 ] == 3
1453
+
1454
+
1455
+ def test_fixed_equivalence (fit_fixed_models ):
1456
+ res , res_fixed = fit_fixed_models
1457
+
1458
+ assert_allclose (res .aic , res_fixed .aic )
1459
+ assert_allclose (res .bic , res_fixed .bic )
1460
+ assert_allclose (res .loglikelihood , res_fixed .loglikelihood )
1461
+ assert res .nobs == res_fixed .nobs
1462
+ assert res .num_params == res_fixed .num_params
1463
+ assert_allclose (res .params , res_fixed .params )
1464
+ assert_allclose (res .conditional_volatility , res_fixed .conditional_volatility )
1465
+ assert_allclose (res .std_resid , res_fixed .std_resid )
1466
+ assert_allclose (res .resid , res_fixed .resid )
1467
+ assert_allclose (res .arch_lm_test (5 ).stat , res_fixed .arch_lm_test (5 ).stat )
1468
+ assert res .model .__class__ is res_fixed .model .__class__
1469
+ assert res .model .volatility .__class__ is res_fixed .model .volatility .__class__
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
+
1475
+
1476
+ @pytest .mark .skipif (not HAS_MATPLOTLIB , reason = "matplotlib not installed" )
1477
+ def test_fixed_equivalence_plots (fit_fixed_models ):
1478
+ res , res_fixed = fit_fixed_models
1479
+
1480
+ fig = res .plot ()
1481
+ fixed_fig = res_fixed .plot ()
1482
+ assert isinstance (fig , type (fixed_fig ))
1483
+
1484
+ close_plots ()
1485
+
1486
+
1487
+ @pytest .mark .parametrize ("simulations" , [1 , 100 ])
1488
+ def test_fixed_equivalence_forecastable (forecastable_model , simulations ):
1489
+ res , res_fixed = forecastable_model
1490
+ f1 = res .forecast (horizon = 5 )
1491
+ f2 = res_fixed .forecast (horizon = 5 )
1492
+ assert isinstance (f1 , type (f2 ))
1493
+ assert_allclose (f1 .mean , f2 .mean )
1494
+ assert_allclose (f1 .variance , f2 .variance )
1495
+
1496
+ f1 = res .forecast (horizon = 5 , method = "simulation" , simulations = simulations )
1497
+ f2 = res_fixed .forecast (horizon = 5 , method = "simulation" , simulations = simulations )
1498
+ assert isinstance (f1 , type (f2 ))
1499
+ f1 = res .forecast (horizon = 5 , method = "bootstrap" , simulations = simulations )
1500
+ f2 = res_fixed .forecast (horizon = 5 , method = "bootstrap" , simulations = simulations )
1501
+ assert isinstance (f1 , type (f2 ))
1502
+
1503
+
1504
+ @pytest .mark .skipif (not HAS_MATPLOTLIB , reason = "matplotlib not installed" )
1505
+ def test_fixed_equivalence_forecastable_plots (forecastable_model ):
1506
+ res , res_fixed = forecastable_model
1507
+ fig1 = res .hedgehog_plot (start = SP500 .shape [0 ] - 25 )
1508
+ fig2 = res_fixed .hedgehog_plot (start = SP500 .shape [0 ] - 25 )
1509
+ assert isinstance (fig1 , type (fig2 ))
1510
+ close_plots ()
0 commit comments