【ボラティリティ変動モデル】ARCHモデルのパラメータ推定

# 使用環境

```env.jl
julia> versioninfo()
Julia Version 1.7.0
Commit 3bf9d17731 (2021-11-30 12:12 UTC)
Platform Info:
  OS: Windows (x86_64-w64-mingw32)
  CPU: Intel(R) Core(TM) i5-10210U CPU @ 1.60GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-12.0.1 (ORCJIT, skylake)

(@v1.7) pkg> st Optim
      Status `C:\Users\user_name\.julia\environments\v1.7\Project.toml`
  [429524aa] Optim v1.5.0
```

# パラメータ推定
[前回](https://qiita.com/takahashi-ry/items/f659288b2fa1903c6e61)はボラティリティ変動モデルとはといった話をしました。今回は、前回紹介したARCHモデルのパラメータ推定を実装していきます。

沖本本や渡部本にあるようにARCHモデルは最尤推定を使ってパラメータを推定します。ただ前回を見てわかるように、ARCHモデルはARモデルに書き換えることができます。なので、ARモデルと同じようなパラメータ推定を行って、ARCHモデルのパラメータを推定することが可能です。

今回は前回とは違い、ARモデルに書き換えることなく、直接ARCHモデルのパラメータを推定したいと思います。

## 方針
ARCHモデルの推定法を調べると以下の２つの方針があることがわかりました。

- 同時推定
- 二段階推定

ARCHモデルは期待値部分のモデルと分散部分のモデルを両方兼ね備えたモデルです。二段階推定とは、期待値部分のモデルパラメータを推定したあとに、分散部分のモデルパラメータを推定します。
私が調べられる範囲では、二段階推定が主流で、同時推定を行っている情報を見つけることはできませんでした。

以上のことを考慮し、実装は二段階推定で行うものとします。

### 同時推定について

今回の実装に当たって、PythonでARCHモデルを記述できる`arch`というパッケージを参考にしました。Pythonのほうは私がソースを見た限りでは二段階推定を行っています。

反対にRの`rugarch`は同時推定を行っている[ようです](https://ichi.pro/python-o-shiyoshita-arima-garch-yosoku-134413039821823)（「ようです」なのはソースを私が読めなかったので確認できていないという、、、）。

いろいろ調べたのですが、同時推定について言及している資料を見つけることがで来ませんでした。ただ、理屈でいえば、パラメータの更新を同時に行えばいいと思うので、

1. 期待値部分のパラメータを初期化する
1. 期待値部分のパラメータを用いて, 残差を計算する
1. 分散部分のパラメータを更新
1. 更新した分散部分のパラメータを使って期待値部分のパラメータを更新
1. 以下2 ~ 4 を指定回数またはパラメータの変化が小さくなるまで続ける

というのが考えられるかなと。
どなたか同時推定について詳しかたがいらっしゃれば教えてください。

### Pythonパッケージ arch について

`arch`は、期待値部分に含められるモデルはあらかじめ決まっています。ざっくりいうとARモデルとそれのちょっとした拡張までです。それ以外のモデルを組み込みたいときは、`statsmodels`などのほかのパッケージを使って、まず期待値のモデル化を行い、残差を求めます。これを所与として分散部分のモデル化を学習させるというフローです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1272355/5620e63b-a203-86de-984e-ea12c214a7c6.png)
https://pypi.org/project/arch/ より抜粋

例1）AR-ARCH
ARは、`arch`だけで学習が完了します

例2）ARMA-ARCH
ARMAは`arch`では書けないので、`arch`だけではなく、`statsmodels`も使います。

Pythonのコードが見たい方はこちらの記事をご覧ください。

https://ichi.pro/python-o-shiyoshita-arima-garch-yosoku-134413039821823

## 対数尤度関数

以下、期待値部分のモデルを $\mu_t$, 分散部分のモデルを $u_t$ とします。また今後特に断りなく$\theta$ を使うときはパラメータをまとめたものと考えてください。

まず私たちの出発地点は、$\epsilon_t$ をホワイトノイズとした観測値 $y$ についての条件付き期待値のモデル化から始まります。

$$
y_t = \mu_t + \epsilon_t \quad \epsilon ～ N( 0, \sigma )　\quad \tag{1}
$$

[前回](https://qiita.com/takahashi-ry/items/f659288b2fa1903c6e61#%E6%A0%AA%E4%BE%A1%E5%8F%8E%E7%9B%8A%E7%8E%87%E3%81%AE%E3%83%A2%E3%83%87%E3%83%AB%E5%8C%96)記事より、残差の二乗に自己相関が現れることを確認しました。
そこで、$\epsilon$ をそれぞれ独立な $u$ と $z ～ N(0,1)$ に分解できると仮定します。すなわち

$$
\epsilon_t = \sqrt{u_t} z_t \quad z_t ～ N(0,1) \quad \tag{2}
$$

ここで、$u_t$ は次のような式で表されます。

$$
u_t = w + \Sigma_{p=1}^{P} \alpha_{t} \epsilon_{t-p} \quad \tag{3} \\
w > 0, \alpha \geq 0
$$

(1),(2)より、

```math
\begin{align}
y_t &= \mu_t + \epsilon_t \\
\epsilon_t &= y_t - \mu_t \\
\sqrt{u_t} z_t &= y_t - \mu_t
\end{align}
```
$z_t ～ N(0, 1)$ であることを考えると、

$$
\epsilon_t ～ N( \mu_t, u_t )
$$

すなわち

$$
f(\epsilon_t|\theta) = -\frac{1}{\sqrt{2\pi u_t}} \exp{(-\frac{1}{2u_t}(y_t - \mu_t^2))}
$$

となります。尤度関数は確率密度の積で表されますが、通常対数をとったものを最適化します。

$$
L(X|\theta) = -\frac{T-p-1}{2} \log{2\pi} 
- \frac{1}{2} \Sigma_{t=1}^{T-p-1} \log{u_{t+p}} 
- \frac{1}{2} \Sigma_{t=1}^{T-p-1} \frac{1}{u_{t+p}}(y_{t+p} - \mu_{t+p})^2 \quad \tag{4}
$$

(4)はプログラムでは次のように書けます。

```loglikelihood.jl
sum( logpdf( Normal( μ, σ ), X )
```

`logpdf`は確率密度の値に対数を取ったものです。

# 観測値の分布について

本記事では何の気になしに正規分布を仮定しましたが、沖本本によれば実証実験では正規分布のほかにt分布やGED（一般化誤差分布）を用いることもあるらしいです。
詳しくは沖本本や渡部本を参考にしてください。

# 実装

使い方の例として`main.jl`を書きました。`estimateParamsOfArch`がARCHモデルのパラメータ推定を行う関数です。第一引数に、期待値部分のモデルの残差の二乗を入れ、第二引数にARCHモデルの次数を選択します。

返り値は、Optim.jlの最適化結果と対数尤度関数を返します。

※AR部分の実装は過去記事の[グレンジャー因果性検定の実装 計量時系列分析4.3 P81～P82](https://qiita.com/takahashi-ry/items/74f39dc7d2304135ab36#%E3%82%BD%E3%83%BC%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89)にあるのですが、使いづらかったので書き直しました。詳しい実装は一番下のソース`new_ar.jl`に書きました。

```main.jl
# size( X ) = ( 100 )
data = diff( log.( X ) )
# ar学習
ar = Q.ar(1)
ar_res = Q.fit( ar, data )
# 残差の2乗
resid2 = Q.resid( ar_res, data ).^2

# ARCH(1)
arch_res, f = estimateParamsOfArch( resid2, 1 )

# 推定されたパラメータ
arch_res.minimizer
```

## ARCHモデルパラメータ推定

以前までの記事では、対数尤度関数もすべて自前で書いていたのですがどうしてもパラメータが収束しなかったので、今回初めてDistributions.jlの力を借りて書いてみました。

```arch.jl
using Optim

"""
    estimateParamsOfArch( r², lags ) -> Optim.MultivariateOptimizationResults{ ... 略 }, function
    
　　r²　: 残差の二乗 
　　lags: archモデルの次数
"""
function estimateParamsOfArch( r², lags )
    X = r²[end:-1:1]
    param = rand( lags + 1 )
    _X = hcat( ( X[begin + i:lags + i] for i in 1:length(X) - lags )... )

    # arch
    ut( x, w ) = w' * [ 1; x ]
    
    # 対数尤度関数
    f( x, X, _X, lags ) = sum( logpdf.( Normal.( 0.0, ut( _X, x )' ), X[begin:end - lags] ) )
    
    # 準ニュートン法を使用 
    # memo 最急降下法だと学習時間がかかりすぎた
    inner_optimizer = BFGS()
    # 最適化の様子を追えるように関数値を内部に保存する
    optim_ops = Optim.Options(
        store_trace=true,
        extended_trace=true,
    )
    
    # パラメータに非負制約を加える
    lower = zeros( lags + 1 )
    upper = [ Inf for _ in 1:lags + 1 ]

    # 最適化 optim.jlはパラメータを最小化するので, 対数尤度関数にマイナス1を掛ける
    res = optimize( 
        x -> -f( x, X, _X, lags ),
        lower,
        upper,
        rand( lags + 1 ),
        Fminbox(inner_optimizer),
        optim_ops
    )
    res, f
end
```

## ARモデルの書き直し

下記コードの`loglikelihood` `loglikelihood2` はうまく収束させることができなかったのですが、供養として。

```new_ar.jl

module Q

using Statistics ; mean
using LinearAlgebra ; dot
using Optim
#using LineSearches
using Distributions

abstract type ConditionalMean end
abstract type ConditionalVolatility end

"""
    arch{T <: Integer} <:ConditionalVolatility

    lags of arch model
"""
struct arch{T <: Integer} <:ConditionalVolatility
    q::T
end

"""
    ar{T <: Integer} <:ConditionalMean

    lags of ar model
"""
struct ar{T <: Integer} <:ConditionalMean
    p::T
end

"""
    q         : lags
    w         : const
    α        : coef
    mean      : params of conditional mean model
    optim_res : return optimize
    ll        : likelihood value
"""
struct ArchResults
    q
    w
    α
    mean # 条件付き期待値のモデルパラメータ
    optim_res
    ll   # 尤度の値を格納する配列
end

"""
    p  : lags
    ϕ : coef
    σ : var
"""
struct ArResults
    p
    ϕ
    σ
end

"""
    μ : const
"""
struct ConstantResults
    μ
end

"""
    lenght of params
"""
Base.length( v::arch ) = v.q + 1

@doc raw"""
    likelihood(a::arch, m::ArResults, x, X, resid, init_var; ρ=1_000_000)

    a          : arch obj
    m          : optimized ar params 
    x          : params to be optimize. x = [ w, α₁, α₂ ... ]
    X          : samples
    resid2     : Squared difference between mean model and obs
    init_var   : variance of resid
    ρ         : penalty coef

# HACK \ はQiitaに張るために入れたものなので、実際に使うときは消す 
\```math
L(\theta) = - \frac{1}{2} \Sigma_{t=1}^{T} h_t - \frac{1}{2} \Sigma_{t=1}^{T} \frac{( y_t - \mu_t )^2}{h_t}
+ \frac{\rho}{w} + \Sigma_{q=1}^{Q} \frac{\rho}{\alpha_q}  
\```
    return log likelihood which have penalty function.
""" 
function loglikelihood1(a::arch, m::ArResults, x, X, resid2, init_var; ρ=1_000_000_000)
    # 残差を二乗し, 初期値を先頭に加える 
    u = vcat( [ init_var for _ in 1:a.q ], resid2 )
    # 条件付き期待値のモデルの初期値を設定する 
    # 沖本本では適当な定数を置くとしている
    # 初期値を0.0と設定
    X = vcat( [ 0.0 for _ in 1:m.p ], X[begin:end-m.p] )
    # 対数尤度の初期化
    ll = 0.0

    @show x
    # 尤度の計算
    for i in 1:length(u) - a.q + 1
        # 条件付き分散
        h = x[1] + x[2:end]' * u[i:a.q+i-1][end:-1:begin]
        # 定数項と第2項
        ll += ( log(2pi) + log(h) ) / 2
        # 第3項
        ll += ( X[i] - m.ϕ[1] - m.ϕ[2:end]' * X[i:m.p+i-1][end:-1:begin] )^2 / 2h 
    end
    # ペナルティ関数
    pen = ρ * ( 1 / x[1] + sum( 1 / x[i] for i=1:a.q ) )
    
    # 尤度関数値
   @show - ll#  + pen
end

function loglikelihood2(a::arch, m::ArResults, x, X, resid2, init_var)
    linear( coef, data ) = coef' * vcat( ones(), data )
    # 初期値を加える
    u = vcat( [ init_var for _ in 1:a.q ], resid2 )

    ll = 0.0 
    for i in 1:length(u) - a.q + 1
        h = linear( x, u[i:i+a.q-1][end:-1:begin] )
        x_ = linear( m.ϕ, X[i:i+m.p-1][end:-1:begin] )
        ll += loglikelihood( Normal( x_, sqrt(h) ), [X[i+a.q-1]] )
    end
    ll
end

#y2   y1 u1 u0   y3  y2 y1
#y10  y9 u9 u8  y10  y9 y8

resid(m::ArResults, X) = X[begin+m.p:end] .- predict(m, X)
resid(c::ConstantResults, X) = X .- c.μ

#===== prediction =====#
"""
    predict(m::ArResults, X)

    predict in samples
"""
function predict(m::ArResults, X)
    res = zeros(length(X) - m.p)
    for i in 0:length(res)-1
        rev_data = X[begin+i:m.p+i][end:-1:1]
        
        basis_func =[ 1 ; rev_data ]
        res[i+1] = dot( m.ϕ, basis_func ) 
    end
    res
end

#===== fit =====#
"""
    fit( m::T, X ) where T <: Real

    estimate ar model params
"""
fit( m::T, X ) where T <: Real = ConstantResults(m.μ)

"""
    fit(m::CM, v::CV, X) where {CM <:ConditionalMean, CV <:ConditionalVolatility}

    estimate arch model params
    mean       : constant or ar
    volatility : arch
"""
function fit(m::CM, v::CV, X) where {CM <:ConditionalMean, CV <:ConditionalVolatility}
    # 条件付き期待値と観測値の残差の2乗を求める
    mean_params = fit( m, X )  
    u = resid( mean_params, X ).^2
    # 残差の二乗の初期値
    u_ini = var(u)
    
    inner_optimizer = GradientDescent()
    optim_ops = Optim.Options(
        store_trace=true,
        extended_trace=true,
    )
    lower = [0.0, 0.0]
    upper = [Inf, Inf]
    
    # 最適化 optim.jlはパラメータを最小化するので, 対数尤度関数にマイナス1を掛ける
    res = optimize( 
        x -> -loglikelihood2( v, mean_params, x, X, u, u_ini ),
        lower,
        upper,
        rand( length(v) ),
        Fminbox(inner_optimizer),
        optim_ops
        )

    res, mean_params,x->loglikelihood2( v, mean_params, x, X, u, u_ini )
    #ArchResults( q, w, α, mean_params, res, ll )
end

"""
    fit( m::ar, X )

    estimate ar model params
"""
function fit( m::ar, X )
    N = size(X)[1]
    
    Φ = zeros( N-m.p, m.p+1 ) 
    # 定数項
    Φ[:,1] .+= 1
    for i in 0 : m.p-1
        Φ[ :,i+2 ] += X[ m.p-i : end-1-i ]
    end

    Y = X[ m.p+1 : end ]

    coef = inv(Φ'Φ)Φ'Y
    σ² = mean( ( Y .- (coef'Φ')' ).^2 )
    
    return ArResults(m.p , coef, σ²)
end
    
end

```

# 参考文献

- [ボラティリティ変動モデル 渡部敏明](https://www.amazon.co.jp/%E3%83%9C%E3%83%A9%E3%83%86%E3%82%A3%E3%83%AA%E3%83%86%E3%82%A3%E5%A4%89%E5%8B%95%E3%83%A2%E3%83%87%E3%83%AB-%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-%E7%8F%BE%E4%BB%A3%E9%87%91%E8%9E%8D%E5%B7%A5%E5%AD%A6-%E6%B8%A1%E9%83%A8-%E6%95%8F%E6%98%8E/dp/4254275048)
- [計量時系列分析 沖本竜義](https://www.amazon.co.jp/%E7%B5%8C%E6%B8%88%E3%83%BB%E3%83%95%E3%82%A1%E3%82%A4%E3%83%8A%E3%83%B3%E3%82%B9%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E8%A8%88%E9%87%8F%E6%99%82%E7%B3%BB%E5%88%97%E5%88%86%E6%9E%90-%E7%B5%B1%E8%A8%88%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%83%BC-%E6%B2%96%E6%9C%AC-%E7%AB%9C%E7%BE%A9/dp/4254127928)
- [arch PyPI](https://pypi.org/project/arch/)
- [rugarch RDocumentation](https://www.rdocumentation.org/packages/rugarch/versions/1.4-4)
- [ARCH型モデルと
“Realized Volatility”による
ボラティリティ予測と
バリュー・アット・リスク 渡部敏明](https://www.imes.boj.or.jp/research/papers/japanese/kk25-b2-2.pdf)
- [ARCH/GARCH Volatility Forecasting](https://goldinlocks.github.io/ARCH_GARCH-Volatility-Forecasting/)