Загрузите Блокнот iPython здесь

В этой мини-серии по моделированию временных рядов для финансовых данных до сих пор мы использовали AR, MA и комбинацию этих моделей для оценки цен на активы, чтобы попытаться смоделировать поведение нашего актива. Мы обнаружили, что с помощью этих моделей мы могли хорошо моделировать определенные периоды времени, а иногда терпели неудачу.

Это произошло из-за кластеризации волатильности или гетероскедастичности. В этом посте мы обсудим условную гетероскедастичность, что приведет нас к нашей первой модели условной гетероскедастичности, известной как ARCH. Затем мы обсудим расширения ARCH, которые приведут нас к известной модели обобщенной авторегрессии с условной гетероскедастичностью порядка p, q, также известной как GARCH (p, q). GARCH широко используется в финансовой индустрии, поскольку цены на многие активы являются условно гетероскедастичными.

Давайте сначала сделаем краткий обзор:

До сих пор мы рассматривали следующие модели в этой серии (рекомендуется прочитать серию по порядку, если вы еще этого не сделали):

Теперь мы подошли к финальной части головоломки. Нам нужна модель для изучения условной гетероскедастичности финансовых рядов, демонстрирующих кластеризацию волатильности.

Что такое условная гетероскедастичность?

В финансах существует условная гетероскедастичность, поскольку доходность активов непостоянна.

Набор случайных величин является гетероскедастичным, если в большом наборе есть подмножества переменных, которые отличаются от остальных переменных.

Рассмотрим день, когда фондовые рынки претерпевают существенное падение. Рынок входит в режим паники, автоматизированные системы управления рисками начинают открывать длинные позиции, продавая их, и все это приводит к дальнейшему падению цен. Увеличение отклонения от первоначального падения цены приводит к значительной дальнейшей волатильности вниз.

То есть увеличение дисперсии последовательно коррелирует с дальнейшим увеличением дисперсии в такой период «распродажи». Или, глядя на это с другой стороны, период повышенной дисперсии обусловлен первоначальной распродажей. Таким образом, мы говорим, что такие ряды условно гетероскедастичны.

Условно гетероскедастические (CH) ряды нестационарны, поскольку их дисперсия непостоянна во времени. Одним из сложных аспектов условных гетероскедастических рядов является то, что графики ACF ряда с волатильностью все еще могут казаться реализацией стационарного дискретного белого шума.

Как мы можем включить CH в нашу модель? Одним из способов может быть создание модели AR для самой дисперсии - модели, которая фактически учитывает изменения дисперсии с течением времени, используя прошлые значения дисперсии.

Это основа модели авторегрессии условной гетероскедастики (ARCH).

Авторегрессионные условно-гетероскедастические модели - ARCH (p)

Модель ARCH (p) - это просто модель AR (p), применяемая к дисперсии временного ряда.

ARCH (1) определяется по формуле:

Var (x (t)) = σ² (t) = ⍺ * σ² (t-1) + ⍺1

Фактический временной ряд определяется следующим образом:

x(t) = w(t)* σ(t) = w(t)* ⎷(⍺*σ²(t-1) + ⍺1)

где w (t) - белый шум

Когда применять ARCH (p)?

Допустим, мы подбираем модель AR (p), и остатки выглядят почти как белый шум, но нас беспокоит уменьшение лага p на графике ACF ряда. Если мы обнаружим, что можем применить AR (p) и к квадрату остатков, то у нас будет указание на то, что процесс ARCH (p) может быть подходящим.

Обратите внимание, что ARCH (p) следует применять только к ряду, для которого уже была установлена ​​соответствующая модель, достаточная для того, чтобы остатки выглядели как дискретный белый шум. Поскольку мы можем определить, подходит ли ARCH, только возведя в квадрат остатки и изучив ACF, нам также необходимо убедиться, что среднее значение остатков равно нулю.

ARCH следует применять только к рядам, которые не имеют тенденций или сезонных эффектов, то есть не имеют (очевидной) серийной корреляции. ARIMA часто применяется к таким сериям, и тогда ARCH может подойти.

# Simulate ARCH(1) series
# Var(yt) = a_0 + a_1*y{t-1}**2
# if a_1 is between 0 and 1 then yt is white noise

np.random.seed(13)

a0 = 2
a1 = .5

y = w = np.random.normal(size=1000)
Y = np.empty_like(y)

for t in range(len(y)):
    y[t] = w[t] * np.sqrt((a0 + a1*y[t-1]**2))

# simulated ARCH(1) series, looks like white noise
tsplot(y, lags=30)

Обратите внимание, что временной ряд выглядит как белый шум. Однако давайте посмотрим, что произойдет, когда мы построим квадрат ряда.

tsplot(y**2, lags=30)

Теперь ACF и PACF, кажется, показывают значимость при задержке 1, указывая на то, что модель AR (1) для дисперсии может быть подходящей.

На этом этапе возникает очевидный вопрос: если мы собираемся применить процесс AR (p) к дисперсии, то почему бы также не использовать модель скользящей средней MA (q)? Или смешанная модель, такая как ARMA (p, q)?

На самом деле это мотивировка для обобщенной модели ARCH, известной как GARCH.

Обобщенные условно-гетероскедастические модели авторегрессии - GARCH (p, q)

Подобно тому, как ARCH (p) - AR (p), применяемый к дисперсии временного ряда, GARCH (p, q) - это ARMA (p, q), примененная к дисперсии временного ряда. AR (p) моделирует дисперсию остатков (квадратов ошибок) или просто возведенного в квадрат нашего временного ряда. Часть MA (q) моделирует дисперсию процесса.

Модель GARCH (1,1):

σ²(t) = a*σ²(t-1) + b*e²(t-1) + w

(a + b) должно быть меньше 1, иначе модель нестабильна. Мы можем смоделировать процесс GARCH (1, 1) ниже.

# Simulating a GARCH(1, 1) process

np.random.seed(2)

a0 = 0.2
a1 = 0.5
b1 = 0.3

n = 10000
w = np.random.normal(size=n)
eps = np.zeros_like(w)
sigsq = np.zeros_like(w)

for i in range(1, n):
    sigsq[i] = a0 + a1*(eps[i-1]**2) + b1*sigsq[i-1]
    eps[i] = w[i] * np.sqrt(sigsq[i])

_ = tsplot(eps, lags=30)

Опять же, обратите внимание, что в целом этот процесс очень похож на белый шум, однако взгляните, когда мы рассмотрим серию eps в квадрате.

_ = tsplot(eps**2, lags=30)

Есть веские доказательства условно гетероскедастического процесса через распад последовательных лагов. Значимость задержек как в ACF, так и в PACF указывает на то, что нам нужны компоненты AR и MA для нашей модели. Давайте посмотрим, сможем ли мы восстановить параметры нашего процесса, используя модель GARCH (1, 1). Здесь мы используем функцию arch_model из пакета ARCH.

# Fit a GARCH(1, 1) model to our simulated EPS series
# We use the arch_model function from the ARCH package
am = arch_model(eps)
res = am.fit(update_freq=5)
print(res.summary())
Iteration:      5,   Func. Count:     38,   Neg. LLF: 12311.7950557
Iteration:     10,   Func. Count:     71,   Neg. LLF: 12238.5926559
Optimization terminated successfully.    (Exit mode 0)
            Current function value: 12237.3032673
            Iterations: 13
            Function evaluations: 89
            Gradient evaluations: 13
                     Constant Mean - GARCH Model Results                      
====================================================================
Dep. Variable:                 y   R-squared:                 -0.000
Mean Model:        Constant Mean   Adj. R-squared:            -0.000
Vol Model:                 GARCH   Log-Likelihood:          -12237.3
Distribution:             Normal   AIC:                      24482.6
Method:       Maximum Likelihood   BIC:                      24511.4
                                   No. Observations:           10000
Date:           Tue, Feb 28 2017   Df Residuals:                9996
Time:                   20:52:48   Df Model:                       4
                             Mean Model                                  
====================================================================
                coef    std err      t  P>|t|     95.0% Conf. Int.
--------------------------------------------------------------------
mu       -6.7225e-03  6.735e-03 -0.998  0.318 [-1.992e-02,6.478e-03]
                            Volatility Model                            
====================================================================
               coef    std err        t      P>|t|  95.0% Conf. Int.
--------------------------------------------------------------------
omega        0.2021  1.043e-02   19.383  1.084e-83 [  0.182,  0.223]
alpha[1]     0.5162  2.016e-02   25.611 1.144e-144 [  0.477,  0.556]
beta[1]      0.2879  1.870e-02   15.395  1.781e-53 [  0.251,  0.325]
====================================================================
Covariance estimator: robust

Мы видим, что все истинные параметры попадают в соответствующие доверительные интервалы.

Применение к финансовым временным рядам

Теперь примените процедуру к финансовому временному ряду. Здесь мы собираемся использовать возврат SPX. Процесс выглядит следующим образом:

  • Перебирайте комбинации моделей ARIMA (p, d, q), чтобы наилучшим образом соответствовать нашему временному ряду.
  • Выберите заказы модели GARCH в соответствии с моделью ARIMA с самым низким AIC.
  • Подгоните модель GARCH (p, q) к нашему временному ряду.
  • Изучите остатки модели и квадраты остатков на предмет автокорреляции.

Здесь мы сначала пытаемся приспособить возврат SPX к процессу ARIMA и найти лучший порядок.

import auquanToolbox.dataloader as dl
end = ‘2017–01–01’
start = ‘2010–01–01’
symbols = [‘SPX’]
data = dl.load_data_nologs(‘nasdaq’, symbols , start, end)[‘ADJ CLOSE’]
# log returns
lrets = np.log(data/data.shift(1)).dropna()
def _get_best_model(TS):
    best_aic = np.inf 
    best_order = None
    best_mdl = None
    pq_rng = range(5) # [0,1,2,3,4]
    d_rng = range(2) # [0,1]
    for i in pq_rng:
        for d in d_rng:
            for j in pq_rng:
                try:
                    tmp_mdl = smt.ARIMA(TS, order=(i,d,j)).fit(
                        method='mle', trend='nc'
                    )
                    tmp_aic = tmp_mdl.aic
                    if tmp_aic < best_aic:
                        best_aic = tmp_aic
                        best_order = (i, d, j)
                        best_mdl = tmp_mdl
                except: continue
    print('aic: {:6.2f} | order: {}'.format(best_aic, best_order))                    
    return best_aic, best_order, best_mdl
TS = lrets.SPX
res_tup = _get_best_model(TS)

aic: -11323.07 | порядок: (3, 0, 3)

order = res_tup[1]
model = res_tup[2]

Поскольку мы уже взяли журнал доходности, мы должны ожидать, что наш интегрированный компонент d будет равен нулю, что он и делает. Мы считаем, что лучшей моделью является ARIMA (3,0,3). Теперь мы построим остатки, чтобы решить, обладают ли они доказательствами условного гетероскедастического поведения.

tsplot(model.resid, lags=30)

Мы находим остатки похожими на белый шум. Давайте посмотрим на квадрат остатков

tsplot(model.resid**2, lags=30)

Мы видим явное свидетельство автокорреляции квадратов остатков. Давайте подберем модель GARCH и посмотрим, как она работает.

# Now we can fit the arch model using the best fit arima model parameters
p_ = order[0]
o_ = order[1]
q_ = order[2]

am = arch_model(model.resid, p=p_, o=o_, q=q_, dist='StudentsT')
res = am.fit(update_freq=5, disp='off')
print(res.summary())
              Constant Mean - GARCH Model Results                         
====================================================================
Dep. Variable:                    None   R-squared:       -56917.881
Mean Model:              Constant Mean   Adj. R-squared:  -56917.881
Vol Model:                       GARCH   Log-Likelihood:    -4173.44
Distribution: Standardized Student's t   AIC:                8364.88
Method:             Maximum Likelihood   BIC:                8414.15
                                         No. Observations:      1764
Date:                 Tue, Feb 28 2017   Df Residuals:          1755
Time:                         20:53:30   Df Model:                 9
                               Mean Model                               
====================================================================
               coef    std err        t      P>|t|  95.0% Conf. Int.
--------------------------------------------------------------------
mu          -2.3189  9.829e-03 -235.934      0.000 [ -2.338, -2.300]
                            Volatility Model                              
====================================================================
               coef  std err      t     P>|t|       95.0% Conf. Int.
--------------------------------------------------------------------
omega    1.2926e-04 2.212e-04 0.584     0.559 [-3.043e-04,5.628e-04]
alpha[1]     0.0170 1.547e-02 1.099     0.272 [-1.332e-02,4.733e-02]
alpha[2]     0.4638 0.207     2.241 2.500e-02    [5.824e-02,  0.869]
alpha[3]     0.5190 0.213     2.437 1.482e-02      [  0.102,  0.937]
beta[1]  7.9655e-05 0.333 2.394e-04     1.000      [ -0.652,  0.652]
beta[2]  3.8056e-05 0.545 6.980e-05     1.000      [ -1.069,  1.069]
beta[3]  1.6184e-03 0.312 5.194e-03     0.996      [ -0.609,  0.612]
                              Distribution                              
====================================================================
               coef    std err        t      P>|t|  95.0% Conf. Int.
--------------------------------------------------------------------
nu           7.7912      0.362   21.531 8.018e-103 [  7.082,  8.500]
====================================================================

Covariance estimator: robust

Давайте снова изобразим остатки

tsplot(res.resid, lags=30)

Графики выглядят как реализация дискретного процесса белого шума, что указывает на хорошее соответствие. Давайте нарисуем квадрат остатков, чтобы убедиться

tsplot(res.resid**2, lags=30)

У нас есть то, что выглядит как реализация дискретного процесса белого шума, что указывает на то, что мы «объяснили» последовательную корреляцию, присутствующую в квадратах остатков, с соответствующей смесью ARIMA (p, d, q) и GARCH (p, q).

Следующие шаги - образец торговой стратегии

Сейчас мы находимся в той точке нашего анализа временных рядов, когда мы изучили ARIMA и GARCH, что позволило нам подогнать комбинацию этих моделей к индексу фондового рынка и определить, достигли ли мы хорошего соответствия или нет.

Следующим шагом является создание прогнозов будущих значений дневной доходности на основе этой комбинации и использование их для создания базовой торговой стратегии для S & P500.

import auquanToolbox.dataloader as dl
end = '2016-11-30'
start = '2000-01-01'
symbols = ['SPX']
data = dl.load_data_nologs('nasdaq', symbols ,
                           start, end)['ADJ CLOSE']
# log returns
lrets = np.log(data/data.shift(1)).dropna()

Обзор стратегии

Давайте попробуем создать простую стратегию, используя наши знания о моделях ARIMA и GARCH. Идея этой стратегии такова:

  • Подбирайте модели ARIMA и GARCH каждый день на основе журнала доходностей S&P 500 за предыдущие T дней.
  • Используйте комбинированную модель, чтобы спрогнозировать доходность на следующий день.
  • Если прогноз положительный, купите акцию, а если отрицательный - сделайте короткую продажу на сегодняшнем закрытии.
  • Если прогноз такой же, как и в предыдущий день, ничего не делайте.

Реализация стратегии

Начнем с выбора подходящего окна До предыдущих дней, которое мы собираемся использовать для составления прогнозов. Мы собираемся использовать T = 252 (1 год), но этот параметр следует оптимизировать, чтобы повысить производительность или уменьшить просадку.

windowLength = 252

Теперь мы попытаемся сгенерировать торговый сигнал для length (data) - T дней.

foreLength = len(lrets) - windowLength
signal = 0*lrets[-foreLength:]

Чтобы протестировать нашу стратегию на исторических данных, давайте каждый день просматриваем торговые данные и подгоняем подходящую модель ARIMA и GARCH к скользящему окну длиной 252. Мы определили функции для соответствия ARIMA и GARCH выше (при условии, что мы пробуем 32 отдельных ARIMA. подходит и подходит для модели GARCH, для каждого дня создание индикатора может занять много времени)

for d in range(foreLength):
    
    # create a rolling window by selecting 
    # values between d+1 and d+T of S&P500 returns
    
    TS = lrets[(1+d):(windowLength+d)] 
    
    # Find the best ARIMA fit 
    # set d = 0 since we've already taken log return of the series
    res_tup = _get_best_model(TS)
    order = res_tup[1]
    model = res_tup[2]
    
    #now that we have our ARIMA fit, we feed this to GARCH model
    p_ = order[0]
    o_ = order[1]
    q_ = order[2]
    
    am = arch_model(model.resid, p=p_, o=o_, q=q_, dist='StudentsT')
    res = am.fit(update_freq=5, disp='off')
    
    # Generate a forecast of next day return using our fitted model
    out = res.forecast(horizon=1, start=None, align='origin')
    
    #Set trading signal equal to the sign of forecasted return
    # Buy if we expect positive returns, sell if negative
      
    signal.iloc[d] = np.sign(out.mean['h.1'].iloc[-1])

Примечание. Бэктест не принимает во внимание комиссию или проскальзывание, поэтому эффективность, достигаемая в реальной торговой системе, будет ниже, чем та, которую вы видите здесь.

Результаты стратегии

Теперь, когда мы сгенерировали наши сигналы, нам нужно сравнить его эффективность с «Покупать и удерживать»: какова была бы наша прибыль, если бы мы просто купили S&P 500 в начале нашего периода тестирования на исторических данных.

returns = pd.DataFrame(index = signal.index, 
                       columns=['Buy and Hold', 'Strategy'])
returns['Buy and Hold'] = lrets[-foreLength:]
returns['Strategy'] = signal['SPX']*returns['Buy and Hold']
eqCurves = pd.DataFrame(index = signal.index, 
                       columns=['Buy and Hold', 'Strategy'])
eqCurves['Buy and Hold']=returns['Buy and Hold'].cumsum()+1
eqCurves['Strategy'] = returns['Strategy'].cumsum()+1
eqCurves['Strategy'].plot(figsize=(10,8))
eqCurves['Buy and Hold'].plot()
plt.legend()
plt.show()

Мы считаем, что эта модель действительно превосходит наивную стратегию «Покупай и держи». Однако модель не всегда работает хорошо, вы можете видеть, что большая часть прибыли произошла за короткие промежутки времени в 2000–2001 и 2008 годах. Похоже, существуют определенные рыночные условия, когда модель работает очень хорошо.

В периоды высокой волатильности или когда у S&P 500 были периоды «распродаж», такие как 2000–2002 или крах 2008–2009 годов, стратегия работает очень хорошо, возможно, потому, что наша модель GARCH хорошо фиксирует условную волатильность. В периоды восходящего тренда для S & P500, таких как бычий период 2002–2007 гг., Модель работает на одном уровне с S&P 500.

В текущем бычьем прогоне с 2009 года модель показала плохие результаты по сравнению с S&P 500. Индекс вел себя как то, что выглядит скорее как стохастический тренд, производительность модели в этот период пострадала.

Здесь есть несколько предостережений: мы не учитываем здесь проскальзывания или торговые издержки, которые могли бы существенно отразиться на прибыли. Кроме того, мы провели тестирование индекса фондовой биржи, а не торгуемого инструмента. В идеале мы должны провести такое же моделирование и тестирование на истории для фьючерсов S & P500 или биржевых фондов (ETF), таких как SPY.

Эта стратегия может быть легко применена к другим индексам фондового рынка, другим регионам, акциям или другим классам активов.

Вам следует попробовать изучить другие инструменты, поиграть с параметрами окна и посмотреть, сможете ли вы улучшить результаты, представленные здесь. Другие улучшения стратегии могут включать в себя покупку / продажу только в том случае, если прогнозируемая доходность больше или меньше определенного порога, включение в стратегию дисперсии прогноза и т. Д.

Если вы найдете интересные стратегии, участвуйте в нашем конкурсе QuantQuest и получайте долю прибыли на своих стратегиях!