Расширенные методы тестирования стационарности рядов: тесты KPSS, Phillips-Perron и другие

Стационарность временного ряда означает постоянство его статистических свойств во времени. Это не академическая абстракция — от корректного определения стационарности зависит выбор модели прогнозирования, риск-менеджмент и конечная прибыльность стратегии. Нестационарные ряды могут привести к ложным корреляциям, переобучению моделей и катастрофическим потерям в реальной торговле.

Проблемы классического теста Дики-Фуллера

Расширенный тест Дики-Фуллера (ADF) стал стандартом де-факто для тестирования стационарности, но его применение в финансовых данных сопряжено с серьезными ограничениями. Основная проблема заключается в том, что ADF тестирует нулевую гипотезу о наличии единичного корня против альтернативы стационарности. Это создает асимметрию в интерпретации результатов: отклонение нулевой гипотезы не гарантирует стационарности, особенно при малых выборках.

В реальной практике я неоднократно сталкивался с ситуациями, когда ADF показывал стационарность ряда, который визуально демонстрировал явные тренды или структурные сдвиги. Это происходит потому, что тест имеет низкую мощность против альтернатив, близких к единичному корню. Кроме того, ADF чувствителен к выбору количества лагов и может давать противоречивые результаты в зависимости от используемого критерия отбора.

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

Тест KPSS (Квятковского-Филлипса-Шмидта-Шина)

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

Математически тест основан на разложении временного ряда на детерминированную и стохастическую компоненты. KPSS тестирует гипотезу о том, что стохастическая компонента имеет постоянную дисперсию (стационарность) против альтернативы случайного блуждания. Статистика теста вычисляется как отношение квадрата кумулятивных остатков к оценке долгосрочной дисперсии.

import numpy as np
import pandas as pd
import yfinance as yf
import matplotlib.pyplot as plt
from statsmodels.tsa.stattools import kpss, adfuller
from statsmodels.tsa.arima.model import ARIMA
import warnings
warnings.filterwarnings('ignore')

# Загружаем данные по волатильному активу
ticker = "TSLA"
data = yf.download(ticker, start="2020-01-01", end="2024-01-01")
prices = data['Close'].dropna()
returns = prices.pct_change().dropna()

def comprehensive_stationarity_test(series, name):
    """
    Комплексное тестирование стационарности с использованием KPSS и ADF
    """
    print(f"\n=== Анализ стационарности для {name} ===")
    
    # Тест ADF
    adf_stat, adf_pvalue, adf_lags, adf_obs, adf_crit, adf_icbest = adfuller(series, autolag='AIC')
    
    # Тест KPSS (trend и level)
    kpss_stat_trend, kpss_pvalue_trend, kpss_lags_trend, kpss_crit_trend = kpss(series, regression='ct')
    kpss_stat_level, kpss_pvalue_level, kpss_lags_level, kpss_crit_level = kpss(series, regression='c')
    
    results = {
        'adf_stat': adf_stat,
        'adf_pvalue': adf_pvalue,
        'adf_critical_1%': adf_crit['1%'],
        'kpss_trend_stat': kpss_stat_trend,
        'kpss_trend_pvalue': kpss_pvalue_trend,
        'kpss_trend_critical_1%': kpss_crit_trend['1%'],
        'kpss_level_stat': kpss_stat_level,
        'kpss_level_pvalue': kpss_pvalue_level,
        'kpss_level_critical_1%': kpss_crit_level['1%']
    }
    
    return results

# Анализируем цены и доходности
price_results = comprehensive_stationarity_test(prices, "Цены TSLA")
return_results = comprehensive_stationarity_test(returns, "Доходности TSLA")

# Создаем визуализацию
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
fig.suptitle('Анализ стационарности: TSLA', fontsize=16, fontweight='bold')

# График цен
axes[0,0].plot(prices.index, prices.values, color='black', linewidth=1.2)
axes[0,0].set_title('Цены закрытия TSLA', fontweight='bold')
axes[0,0].set_ylabel('Цена ($)')
axes[0,0].grid(True, alpha=0.3)

# График доходностей
axes[0,1].plot(returns.index, returns.values, color='darkgray', linewidth=0.8)
axes[0,1].set_title('Дневные доходности TSLA', fontweight='bold')
axes[0,1].set_ylabel('Доходность')
axes[0,1].grid(True, alpha=0.3)

# Гистограмма доходностей
axes[1,0].hist(returns.values, bins=50, color='lightgray', edgecolor='black', alpha=0.7)
axes[1,0].set_title('Распределение доходностей', fontweight='bold')
axes[1,0].set_xlabel('Доходность')
axes[1,0].set_ylabel('Частота')

# Rolling стандартное отклонение
rolling_std = returns.rolling(window=30).std()
axes[1,1].plot(rolling_std.index, rolling_std.values, color='black', linewidth=1)
axes[1,1].set_title('30-дневная скользящая волатильность', fontweight='bold')
axes[1,1].set_ylabel('Волатильность')
axes[1,1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# Интерпретация результатов
def interpret_stationarity_tests(results, name):
    """
    Интерпретация результатов тестов стационарности
    """
    print(f"\n=== Интерпретация результатов для {name} ===")
    
    # ADF тест
    adf_conclusion = "стационарен" if results['adf_pvalue'] < 0.01 else "нестационарен"
    print(f"ADF тест: p-value = {results['adf_pvalue']:.4f}, ряд {adf_conclusion}")
    
    # KPSS тест с трендом
    kpss_trend_conclusion = "нестационарен" if results['kpss_trend_pvalue'] < 0.01 else "стационарен"
    print(f"KPSS (тренд): p-value = {results['kpss_trend_pvalue']:.4f}, ряд {kpss_trend_conclusion}")
    
    # KPSS тест уровня
    kpss_level_conclusion = "нестационарен" if results['kpss_level_pvalue'] < 0.01 else "стационарен"
    print(f"KPSS (уровень): p-value = {results['kpss_level_pvalue']:.4f}, ряд {kpss_level_conclusion}")
    
    # Общее заключение
    if results['adf_pvalue'] < 0.01 and results['kpss_trend_pvalue'] > 0.01:
        print("Заключение: Ряд стационарен (согласие тестов)")
    elif results['adf_pvalue'] > 0.01 and results['kpss_trend_pvalue'] < 0.01:
        print("Заключение: Ряд нестационарен (согласие тестов)")
    else:
        print("Заключение: Противоречивые результаты, требуется дополнительный анализ")

interpret_stationarity_tests(price_results, "Цены TSLA")
interpret_stationarity_tests(return_results, "Доходности TSLA")

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

Рис. 1: Комплексный анализ стационарности акций TSLA, включающий временные ряды цен и доходностей, распределение доходностей и динамику волатильности. Верхние панели демонстрируют характерное поведение нестационарного ценового ряда с выраженным трендом и стационарного ряда доходностей с постоянным средним. Нижние панели показывают нормальность распределения доходностей и кластеризацию волатильности

=== Интерпретация результатов для Цены TSLA ===
ADF тест: p-value = 0.1396, ряд нестационарен
KPSS (тренд): p-value = 0.0100, ряд стационарен
KPSS (уровень): p-value = 0.0100, ряд стационарен
Заключение: Противоречивые результаты, требуется дополнительный анализ

=== Интерпретация результатов для Доходности TSLA ===
ADF тест: p-value = 0.0000, ряд стационарен
KPSS (тренд): p-value = 0.1000, ряд стационарен
KPSS (уровень): p-value = 0.0312, ряд стационарен
Заключение: Ряд стационарен (согласие тестов)

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

Особое внимание следует уделить версиям теста KPSS с константой (regression=’c’) и трендом (regression=’ct’). Первая проверяет стационарность вокруг постоянного уровня, вторая — вокруг детерминированного тренда. Для финансовых доходностей обычно используется версия с константой, поскольку мы не ожидаем наличия долгосрочного тренда в доходностях.

Критические значения и мощность теста KPSS

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

Тест Филлипса-Перрона (Phillips-Perron)

Тест Phillips-Perron представляет собой усовершенствованную версию теста Дики-Фуллера, которая устраняет основные недостатки последнего. Главное преимущество PP заключается в использовании непараметрической коррекции для учета автокорреляции и гетероскедастичности в остатках без необходимости включения дополнительных лагов в регрессионное уравнение.

В отличие от ADF, где выбор количества лагов может значительно влиять на результаты теста, PP автоматически корректирует статистику с помощью оценки Ньюи-Веста долгосрочной дисперсии. Это делает тест более робастным к спецификации модели и особенно полезным при анализе высокочастотных финансовых данных с сложной структурой автокорреляции.

!pip install arch
from arch.unitroot import PhillipsPerron
import scipy.stats as stats

def advanced_phillips_perron_analysis(series, name, lags_list=[5, 10, 20]):
    """
    Расширенный анализ с тестом Phillips-Perron для различных спецификаций
    """
    print(f"\n=== Phillips-Perron анализ для {name} ===")
    
    results = {}
    
    for lags in lags_list:
        # Тест с константой
        pp_const = PhillipsPerron(series, lags=lags, trend='c')
        
        # Тест с константой и трендом  
        pp_trend = PhillipsPerron(series, lags=lags, trend='ct')
        
        # Тест без константы
        pp_none = PhillipsPerron(series, lags=lags, trend='n')
        
        results[lags] = {
            'const': {'stat': pp_const.stat, 'pvalue': pp_const.pvalue, 'critical_values': pp_const.critical_values},
            'trend': {'stat': pp_trend.stat, 'pvalue': pp_trend.pvalue, 'critical_values': pp_trend.critical_values},
            'none': {'stat': pp_none.stat, 'pvalue': pp_none.pvalue, 'critical_values': pp_none.critical_values}
        }
        
        print(f"\nЛаги: {lags}")
        print(f"Константа: stat={pp_const.stat:.4f}, p-value={pp_const.pvalue:.4f}")
        print(f"Тренд: stat={pp_trend.stat:.4f}, p-value={pp_trend.pvalue:.4f}")
        print(f"Без тренда: stat={pp_none.stat:.4f}, p-value={pp_none.pvalue:.4f}")
    
    return results

# Применяем PP тест к различным трансформациям данных
log_prices = np.log(prices)
diff_log_prices = log_prices.diff().dropna()

pp_prices = advanced_phillips_perron_analysis(prices, "Цены TSLA")
pp_log_prices = advanced_phillips_perron_analysis(log_prices, "Логарифм цен TSLA")
pp_returns = advanced_phillips_perron_analysis(returns, "Доходности TSLA")
pp_diff_log = advanced_phillips_perron_analysis(diff_log_prices, "Лог-доходности TSLA")

# Анализ чувствительности к выбору лагов
def lag_sensitivity_analysis(series, name, max_lags=30):
    """
    Анализ чувствительности PP теста к выбору количества лагов
    """
    lags_range = range(1, max_lags + 1)
    statistics = []
    pvalues = []
    
    for lags in lags_range:
        try:
            pp_test = PhillipsPerron(series, lags=lags, trend='c')
            statistics.append(pp_test.stat)
            pvalues.append(pp_test.pvalue)
        except:
            statistics.append(np.nan)
            pvalues.append(np.nan)
    
    # Визуализация
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))
    
    ax1.plot(lags_range, statistics, color='black', linewidth=2, marker='o', markersize=3)
    ax1.axhline(y=-2.86, color='red', linestyle='--', alpha=0.7, label='5% критическое значение')
    ax1.set_title(f'PP статистика vs количество лагов\n{name}', fontweight='bold')
    ax1.set_xlabel('Количество лагов')
    ax1.set_ylabel('PP статистика')
    ax1.grid(True, alpha=0.3)
    ax1.legend()
    
    ax2.plot(lags_range, pvalues, color='darkgray', linewidth=2, marker='s', markersize=3)
    ax2.axhline(y=0.05, color='red', linestyle='--', alpha=0.7, label='5% уровень значимости')
    ax2.set_title(f'P-значение vs количество лагов\n{name}', fontweight='bold')
    ax2.set_xlabel('Количество лагов')
    ax2.set_ylabel('P-значение')
    ax2.grid(True, alpha=0.3)
    ax2.legend()
    
    plt.tight_layout()
    plt.show()
    
    return lags_range, statistics, pvalues

# Анализ чувствительности для доходностей
lag_analysis = lag_sensitivity_analysis(returns, "Доходности TSLA")
=== Phillips-Perron анализ для Цены TSLA ===

Лаги: 5
Константа: stat=-2.2509, p-value=0.1883
Тренд: stat=-2.0839, p-value=0.5551
Без тренда: stat=-0.0996, p-value=0.6502

Лаги: 10
Константа: stat=-2.2895, p-value=0.1754
Тренд: stat=-2.1523, p-value=0.5167
Без тренда: stat=-0.1418, p-value=0.6352

Лаги: 20
Константа: stat=-2.3089, p-value=0.1691
Тренд: stat=-2.1881, p-value=0.4965
Без тренда: stat=-0.1582, p-value=0.6292

=== Phillips-Perron анализ для Логарифм цен TSLA ===

Лаги: 5
Константа: stat=-3.1738, p-value=0.0215
Тренд: stat=-2.5315, p-value=0.3124
Без тренда: stat=1.1518, p-value=0.9351

Лаги: 10
Константа: stat=-3.1505, p-value=0.0230
Тренд: stat=-2.5440, p-value=0.3063
Без тренда: stat=1.1017, p-value=0.9292

Лаги: 20
Константа: stat=-3.1217, p-value=0.0250
Тренд: stat=-2.5659, p-value=0.2958
Без тренда: stat=1.0299, p-value=0.9200

=== Phillips-Perron анализ для Доходности TSLA ===

Лаги: 5
Константа: stat=-31.9351, p-value=0.0000
Тренд: stat=-32.0635, p-value=0.0000
Без тренда: stat=-31.8071, p-value=0.0000

Лаги: 10
Константа: stat=-31.9965, p-value=0.0000
Тренд: stat=-32.0966, p-value=0.0000
Без тренда: stat=-31.9056, p-value=0.0000

Лаги: 20
Константа: stat=-32.1572, p-value=0.0000
Тренд: stat=-32.1759, p-value=0.0000
Без тренда: stat=-32.1712, p-value=0.0000

=== Phillips-Perron анализ для Лог-доходности TSLA ===

Лаги: 5
Константа: stat=-32.1047, p-value=0.0000
Тренд: stat=-32.2006, p-value=0.0000
Без тренда: stat=-32.0490, p-value=0.0000

Лаги: 10
Константа: stat=-32.1676, p-value=0.0000
Тренд: stat=-32.2411, p-value=0.0000
Без тренда: stat=-32.1289, p-value=0.0000

Лаги: 20
Константа: stat=-32.3286, p-value=0.0000
Тренд: stat=-32.3381, p-value=0.0000
Без тренда: stat=-32.3374, p-value=0.0000

Анализ чувствительности теста Phillips-Perron к выбору количества лагов для ряда доходностей TSLA. Левый график показывает стабильность тест-статистики относительно критического значения, правый график демонстрирует устойчивость p-значений к изменению спецификации модели

Рис. 2: Анализ чувствительности теста Phillips-Perron к выбору количества лагов для ряда доходностей TSLA. Левый график показывает стабильность тест-статистики относительно критического значения, правый график демонстрирует устойчивость p-значений к изменению спецификации модели

Код демонстрирует ключевое преимущество теста Phillips-Perron — его стабильность при различных выборах параметров модели. В отличие от ADF, где неправильный выбор количества лагов может кардинально изменить результат, PP показывает более консистентные результаты. Это особенно важно при автоматизированном тестировании больших наборов активов, где ручная настройка параметров для каждого инструмента невозможна.

👉🏻  Основные метрики для анализа финансовых и биржевых данных

Анализ различных спецификаций (с константой, трендом, без детерминированных компонент) позволяет выявить природу нестационарности. Если тест с трендом показывает стационарность, а тест с константой — нет, это указывает на наличие детерминированного тренда. Такая информация может быть использована для правильной предобработки данных перед построением прогнозных моделей.

Сравнительный анализ мощности тестов

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

Тесты со структурными сдвигами

Тест Зивота-Эндрюса (Zivot-Andrews)

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

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

Тест Зивота-Эндрюса решает эту проблему. Тест эндогенно определяет наиболее вероятное время сдвига, максимизируя статистику теста по всем возможным точкам разлома. Это кардинально меняет критические значения и интерпретацию результатов.

Хотя тест Зивота-Эндрюса представляет значительное улучшение по сравнению с классическими методами, его ограничение одним структурным сдвигом может быть недостаточным для длинных финансовых временных рядов. Реальные активы испытывают множественные структурные изменения на протяжении своей торговой истории, связанные с различными макроэкономическими событиями, изменениями в отраслевой структуре или корпоративными действиями. Вот почему тест Zivot-Andrews профессионалы сегодня применяют редко, предпочитая ему тест Перрона с учетом множественных структурных сдвигов.

Тест Перрона

Тест Перрона (Perron test) и его модификации позволяют тестировать стационарность в присутствии множественных структурных сдвигов с известными или неизвестными датами. Это делает анализ более реалистичным и практически применимым для долгосрочных исторических данных.

!pip install ruptures
from statsmodels.tsa.regime_switching.markov_regression import MarkovRegression
from scipy import stats
import ruptures as rpt
# Расширяем анализируемый период для захвата большего числа сдвигов
extended_data = yf.download("TSLA", start="2020-01-01", end="2025-09-01")
extended_prices = extended_data['Close'].dropna()
extended_dates = extended_prices.index
def multiple_breakpoint_analysis(series, dates, name, min_size=50):
"""
Анализ множественных структурных сдвигов с использованием алгоритма ruptures
"""
print(f"\n=== Анализ множественных сдвигов для {name} ===")
# Нормализуем ряд для лучшей работы алгоритма
normalized_series = (series - np.mean(series)) / np.std(series)
# Различные алгоритмы детекции
algorithms = {
'Pelt': rpt.Pelt(model="rbf"),
'BottomUp': rpt.BottomUp(model="l2"), 
'Window': rpt.Window(width=min_size, model="l2")
}
results = {}
for algo_name, algo in algorithms.items():
try:
algo.fit(normalized_series.reshape(-1, 1))
breakpoints = algo.predict(pen=1.0)  # Penalty parameter
# Убираем последнюю точку (конец ряда)
if len(breakpoints) > 0 and breakpoints[-1] == len(series):
breakpoints = breakpoints[:-1]
# Конвертируем в даты
breakpoint_dates = [dates[bp] for bp in breakpoints if bp < len(dates)]
results[algo_name] = {
'breakpoints': breakpoints,
'breakpoint_dates': breakpoint_dates,
'n_breaks': len(breakpoints)
}
print(f"\n{algo_name}:")
print(f"Количество сдвигов: {len(breakpoints)}")
print(f"Даты сдвигов: {[str(d.date()) for d in breakpoint_dates]}")
except Exception as e:
print(f"Ошибка в алгоритме {algo_name}: {e}")
results[algo_name] = None
return results
# Применяем анализ множественных сдвигов
breakpoint_results = multiple_breakpoint_analysis(
extended_prices.values, 
extended_dates, 
"TSLA"
)
def regime_based_stationarity_test(series, breakpoints, name):
"""
Тестирование стационарности в рамках обнаруженных режимов
"""
print(f"\n=== Режимный анализ стационарности для {name} ===")
if not breakpoints:
print("Структурные сдвиги не обнаружены")
return
# Добавляем границы ряда
all_points = [0] + sorted(breakpoints) + [len(series)]
regime_results = []
for i in range(len(all_points) - 1):
start_idx = all_points[i]
end_idx = all_points[i + 1]
regime_series = series[start_idx:end_idx]
if len(regime_series) < 30:  # Слишком короткий режим
continue
# Тестируем стационарность в режиме
try:
adf_result = adfuller(regime_series, autolag='AIC')
kpss_result = kpss(regime_series, regression='c')
regime_info = {
'regime': i + 1,
'start_idx': start_idx,
'end_idx': end_idx,
'length': len(regime_series),
'adf_stat': adf_result[0],
'adf_pvalue': adf_result[1],
'kpss_stat': kpss_result[0],
'kpss_pvalue': kpss_result[1],
'mean': np.mean(regime_series),
'std': np.std(regime_series)
}
regime_results.append(regime_info)
print(f"\nРежим {i+1} (индексы {start_idx}-{end_idx}, длина {len(regime_series)}):")
print(f"Среднее: {np.mean(regime_series):.2f}, СКО: {np.std(regime_series):.2f}")
print(f"ADF: статистика={adf_result[0]:.4f}, p-value={adf_result[1]:.4f}")
print(f"KPSS: статистика={kpss_result[0]:.4f}, p-value={kpss_result[1]:.4f}")
# Заключение по режиму
if adf_result[1] < 0.05 and kpss_result[1] > 0.05:
print("Заключение: Стационарен в данном режиме")
elif adf_result[1] > 0.05 and kpss_result[1] < 0.05:
print("Заключение: Нестационарен в данном режиме")
else:
print("Заключение: Неопределенный результат")
except Exception as e:
print(f"Ошибка в анализе режима {i+1}: {e}")
return regime_results
# Используем результаты алгоритма Pelt для режимного анализа
if breakpoint_results['Pelt']:
regime_analysis = regime_based_stationarity_test(
extended_prices.values, 
breakpoint_results['Pelt']['breakpoints'], 
"TSLA"
)
# Визуализация множественных сдвигов
def visualize_multiple_breaks(series, dates, breakpoint_results, name):
"""
Визуализация результатов детекции множественных структурных сдвигов
"""
fig, axes = plt.subplots(3, 1, figsize=(16, 12))
fig.suptitle(f'Анализ множественных структурных сдвигов: {name}', fontsize=16, fontweight='bold')
colors = ['red', 'blue', 'green', 'orange', 'purple']
for i, (algo_name, result) in enumerate(breakpoint_results.items()):
if result is None:
continue
ax = axes[i] if i < 3 else axes[-1]
# Основной график
ax.plot(dates, series, color='black', linewidth=1, alpha=0.8, label='Цена')
# Отмечаем точки сдвигов
for bp_date in result['breakpoint_dates']:
ax.axvline(x=bp_date, color=colors[i % len(colors)], 
linestyle='--', alpha=0.7, linewidth=2)
ax.set_title(f'{algo_name}: {result["n_breaks"]} структурных сдвигов', fontweight='bold')
ax.set_ylabel('Цена')
ax.grid(True, alpha=0.3)
ax.legend()
plt.tight_layout()
plt.show()
visualize_multiple_breaks(extended_prices.values, extended_dates, breakpoint_results, "TSLA")
# Статистическое сравнение режимов
def statistical_regime_comparison(series, breakpoints, dates, name):
"""
Статистическое сравнение характеристик различных режимов с ANOVA тестом
"""
if not breakpoints:
return
all_points = [0] + sorted(breakpoints) + [len(series)]
regime_stats = []
for i in range(len(all_points) - 1):
start_idx = all_points[i]
end_idx = all_points[i + 1]
regime_series = series[start_idx:end_idx]
if len(regime_series) < 10: continue stats_dict = { 'regime': i + 1, 'start_date': dates[start_idx], 'end_date': dates[min(end_idx - 1, len(dates) - 1)], 'length': len(regime_series), 'mean': np.mean(regime_series), 'std': np.std(regime_series), 'min': np.min(regime_series), 'max': np.max(regime_series), 'skewness': float(stats.skew(regime_series)), 'kurtosis': float(stats.kurtosis(regime_series)) } regime_stats.append(stats_dict) regime_df = pd.DataFrame(regime_stats) print(f"\n=== Статистическое сравнение режимов для {name} ===") print(regime_df.round(4)) # ANOVA тест на равенство средних между режимами if len(regime_stats) >= 2:
regime_series_list = [np.ravel(np.array(series[all_points[i]:all_points[i+1]]))
for i in range(len(all_points)-1)
if len(series[all_points[i]:all_points[i+1]]) >= 10]
if len(regime_series_list) >= 2:
f_stat, p_value = stats.f_oneway(*regime_series_list)
f_stat = float(f_stat)
p_value = float(p_value)
print(f"\nANOVA тест на равенство средних:")
print(f"F-статистика: {f_stat:.4f}")
print(f"P-значение: {p_value:.4f}")
print(f"Заключение: {'Режимы значимо различаются' if p_value < 0.05 else 'Различия незначимы'}")
return regime_df
# Применяем статистическое сравнение
if breakpoint_results['Pelt']:
regime_comparison = statistical_regime_comparison(
extended_prices.values,
breakpoint_results['Pelt']['breakpoints'],
extended_dates,
"TSLA"
)
=== Анализ множественных сдвигов для TSLA ===
Pelt:
Количество сдвигов: 48
Даты сдвигов: ['2020-04-28', '2020-07-01', '2020-08-20', '2020-11-20', '2020-12-07', '2021-01-06', '2021-02-19', '2021-05-10', '2021-06-22', '2021-09-01', '2021-10-14', '2021-10-28', '2021-12-03', '2021-12-27', '2022-01-18', '2022-02-15', '2022-03-23', '2022-04-21', '2022-05-05', '2022-05-12', '2022-07-26', '2022-10-05', '2022-11-09', '2022-12-15', '2023-01-24', '2023-01-31', '2023-04-20', '2023-05-25', '2023-06-09', '2023-08-08', '2023-08-29', '2023-10-18', '2023-11-15', '2024-01-16', '2024-03-06', '2024-07-01', '2024-07-23', '2024-09-11', '2024-11-06', '2024-12-05', '2024-12-12', '2024-12-27', '2025-02-05', '2025-02-27', '2025-03-06', '2025-04-25', '2025-05-09', '2025-06-02']
BottomUp:
Количество сдвигов: 38
Даты сдвигов: ['2020-04-14', '2020-07-01', '2020-08-20', '2020-11-20', '2021-01-06', '2021-02-19', '2021-05-10', '2021-06-22', '2021-09-01', '2021-10-21', '2021-10-28', '2021-12-03', '2022-01-25', '2022-03-23', '2022-04-21', '2022-05-05', '2022-07-26', '2022-10-05', '2022-11-09', '2022-12-22', '2023-01-24', '2023-04-20', '2023-05-18', '2023-06-09', '2023-10-18', '2023-11-15', '2024-01-16', '2024-03-06', '2024-07-01', '2024-07-23', '2024-09-18', '2024-11-06', '2024-12-05', '2024-12-12', '2024-12-27', '2025-02-05', '2025-02-27', '2025-05-09']
Window:
Количество сдвигов: 17
Даты сдвигов: ['2020-08-20', '2020-12-14', '2021-02-19', '2021-10-21', '2022-01-25', '2022-05-05', '2022-07-19', '2022-10-05', '2022-12-08', '2023-01-31', '2023-06-02', '2023-10-18', '2024-01-16', '2024-07-01', '2024-11-20', '2025-02-20', '2025-05-09']
=== Режимный анализ стационарности для TSLA ===
Режим 1 (индексы 0-80, длина 80):
Среднее: 41.78, СКО: 9.25
ADF: статистика=-1.5476, p-value=0.5098
KPSS: статистика=0.1786, p-value=0.1000
Заключение: Неопределенный результат
Режим 2 (индексы 80-125, длина 45):
Среднее: 58.57, СКО: 6.21
ADF: статистика=-0.0266, p-value=0.9563
KPSS: статистика=0.9590, p-value=0.0100
Заключение: Нестационарен в данном режиме
Режим 3 (индексы 125-160, длина 35):
Среднее: 100.46, СКО: 10.14
ADF: статистика=-1.6943, p-value=0.4341
KPSS: статистика=0.4922, p-value=0.0434
Заключение: Нестационарен в данном режиме
Режим 4 (индексы 160-225, длина 65):
Среднее: 141.22, СКО: 9.43
ADF: статистика=-4.7775, p-value=0.0001
KPSS: статистика=0.0912, p-value=0.1000
Заключение: Стационарен в данном режиме
Режим 7 (индексы 255-285, длина 30):
Среднее: 278.65, СКО: 10.15
ADF: статистика=-3.7201, p-value=0.0038
KPSS: статистика=0.2426, p-value=0.1000
Заключение: Стационарен в данном режиме
Режим 8 (индексы 285-340, длина 55):
Среднее: 228.29, СКО: 13.71
ADF: статистика=-2.3098, p-value=0.1688
KPSS: статистика=0.2095, p-value=0.1000
Заключение: Неопределенный результат
Режим 9 (индексы 340-370, длина 30):
Среднее: 200.90, СКО: 6.09
ADF: статистика=-2.4834, p-value=0.1196
KPSS: статистика=0.2553, p-value=0.1000
Заключение: Неопределенный результат
Режим 10 (индексы 370-420, длина 50):
Среднее: 226.92, СКО: 9.11
ADF: статистика=-2.0015, p-value=0.2859
KPSS: статистика=0.6836, p-value=0.0150
Заключение: Нестационарен в данном режиме
Режим 11 (индексы 420-450, длина 30):
Среднее: 254.90, СКО: 7.57
ADF: статистика=1.1294, p-value=0.9955
KPSS: статистика=0.7988, p-value=0.0100
Заключение: Нестационарен в данном режиме
Режим 21 (индексы 595-645, длина 50):
Среднее: 238.31, СКО: 13.69
ADF: статистика=-2.4782, p-value=0.1209
KPSS: статистика=0.2278, p-value=0.1000
Заключение: Неопределенный результат
Режим 22 (индексы 645-695, длина 50):
Среднее: 288.98, СКО: 15.37
ADF: статистика=-1.7943, p-value=0.3833
KPSS: статистика=0.2210, p-value=0.1000
Заключение: Неопределенный результат
Режим 27 (индексы 775-830, длина 55):
Среднее: 191.59, СКО: 9.88
ADF: статистика=-2.1731, p-value=0.2161
KPSS: статистика=0.3166, p-value=0.1000
Заключение: Неопределенный результат
Режим 30 (индексы 865-905, длина 40):
Среднее: 264.95, СКО: 12.26
ADF: статистика=-2.4833, p-value=0.1196
KPSS: статистика=0.2556, p-value=0.1000
Заключение: Неопределенный результат
Режим 32 (индексы 920-955, длина 35):
Среднее: 257.04, СКО: 9.07
ADF: статистика=-2.9848, p-value=0.0363
KPSS: статистика=0.1063, p-value=0.1000
Заключение: Стационарен в данном режиме
Режим 34 (индексы 975-1015, длина 40):
Среднее: 242.12, СКО: 8.68
ADF: статистика=-0.9862, p-value=0.7583
KPSS: статистика=0.2168, p-value=0.1000
Заключение: Неопределенный результат
Режим 35 (индексы 1015-1050, длина 35):
Среднее: 195.67, СКО: 10.33
ADF: статистика=-2.7938, p-value=0.0592
KPSS: статистика=0.2777, p-value=0.1000
Заключение: Неопределенный результат
Режим 36 (индексы 1050-1130, длина 80):
Среднее: 174.53, СКО: 10.17
ADF: статистика=-2.6206, p-value=0.0888
KPSS: статистика=0.5040, p-value=0.0408
Заключение: Нестационарен в данном режиме
Режим 38 (индексы 1145-1180, длина 35):
Среднее: 214.31, СКО: 11.44
ADF: статистика=-3.2207, p-value=0.0188
KPSS: статистика=0.1477, p-value=0.1000
Заключение: Стационарен в данном режиме
Режим 39 (индексы 1180-1220, длина 40):
Среднее: 240.63, СКО: 15.72
ADF: статистика=-2.0383, p-value=0.2700
KPSS: статистика=0.0980, p-value=0.1000
Заключение: Неопределенный результат
Режим 46 (индексы 1300-1335, длина 35):
Среднее: 251.18, СКО: 17.35
ADF: статистика=-3.0333, p-value=0.0319
KPSS: статистика=0.1210, p-value=0.1000
Заключение: Стационарен в данном режиме
Режим 49 (индексы 1360-1423, длина 63):
Среднее: 323.20, СКО: 14.80
ADF: статистика=-3.3268, p-value=0.0137
KPSS: статистика=0.3588, p-value=0.0949
Заключение: Стационарен в данном режиме

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

Рис. 3: Детекция множественных структурных сдвигов в котировках акций TSLA с использованием различных алгоритмов. Каждая панель показывает результаты отдельного алгоритма детекции, демонстрируя различную чувствительность к структурным изменениям и устойчивость к выбросам

=== Статистическое сравнение режимов для TSLA ===
regime start_date   end_date  length      mean      std       min       max  skewness  kurtosis
0        1 2020-01-02 2020-04-27      80   41.7814   9.2493   24.0813   61.1613    0.2146   -1.0644
1        2 2020-04-28 2020-06-30      45   58.5747   6.2142   46.7547   71.9873    0.3020   -1.1875
2        3 2020-07-01 2020-08-19      35  100.4630  10.1377   74.6420  125.8060    0.4262    1.5213
3        4 2020-08-20 2020-11-19      65  141.2170   9.4282  110.0700  166.4233   -0.0626    1.8380
4        5 2020-11-20 2020-12-04      10  188.0067  10.8097  163.2033  199.6800   -1.1700    0.2995
5        6 2020-12-07 2021-01-05      20  219.5482  11.8696  201.4933  245.0367    0.6839   -0.3330
6        7 2021-01-06 2021-02-18      30  278.6513  10.1467  251.9933  294.3633   -0.5798   -0.1846
7        8 2021-02-19 2021-05-07      55  228.2919  13.7076  187.6667  260.4333   -0.3351    0.5081
8        9 2021-05-10 2021-06-21      30  200.8977   6.0909  187.8200  210.2833   -0.4232   -0.8080
9       10 2021-06-22 2021-08-31      50  226.9222   9.1128  207.9033  245.2400    0.0757   -1.0479
10      11 2021-09-01 2021-10-13      30  254.8992   7.5651  243.3900  270.3600    0.1879   -1.0320
11      12 2021-10-14 2021-10-27      10  304.8787  25.7909  272.7733  345.9533    0.5886   -1.2622
12      13 2021-10-28 2021-12-02      25  372.0607  20.1213  337.7967  409.9700    0.3011   -0.7686
13      14 2021-12-03 2021-12-23      15  329.7836  16.7805  299.9800  356.3200   -0.0232   -1.0235
14      15 2021-12-27 2022-01-14      15  360.7729  14.3602  342.3200  399.9267    1.3021    1.4390
15      16 2022-01-18 2022-02-14      20  306.9555  15.9794  276.3667  343.5033    0.2944    0.1161
16      17 2022-02-15 2022-03-22      25  283.8647  17.6345  254.6800  331.3267    0.5959    0.2660
17      18 2022-03-23 2022-04-20      20  346.9395  15.9264  325.3100  381.8167    0.3928   -0.9163
18      19 2022-04-21 2022-05-04      10  309.4287  18.1345  290.2533  336.2600    0.4602   -1.5061
19      21 2022-05-12 2022-07-25      50  238.3087  13.6862  209.3867  272.2433    0.3553    0.3285
20      22 2022-07-26 2022-10-04      50  288.9801  15.3727  242.4000  309.3200   -0.9117    0.5922
21      23 2022-10-05 2022-11-08      25  218.4856  11.0304  191.3000  240.8100   -0.4026    0.3764
22      24 2022-11-09 2022-12-14      25  181.0460  10.5807  156.8000  195.9700   -0.4602   -0.4535
23      25 2022-12-15 2023-01-23      25  126.6400  13.1616  108.1000  157.6700    0.6849   -0.2847
24      27 2023-01-31 2023-04-19      55  191.5924   9.8772  172.9200  214.2400    0.1593   -0.5773
25      28 2023-04-20 2023-05-24      25  168.5756   8.6264  153.7500  188.8700    0.7525   -0.1356
26      29 2023-05-25 2023-06-08      10  210.2570  14.4479  184.4700  234.8600   -0.1174   -0.7914
27      30 2023-06-09 2023-08-07      40  264.9495  12.2647  241.0500  293.3400    0.5155   -0.2019
28      31 2023-08-08 2023-08-28      15  234.7793   9.1286  215.4900  249.7000   -0.5459   -0.3619
29      32 2023-08-29 2023-10-17      35  257.0409   9.0717  240.5000  276.0400    0.2912   -0.6441
30      33 2023-10-18 2023-11-14      20  216.0250  10.7286  197.3600  242.6800    0.6700    0.4942
31      34 2023-11-15 2024-01-12      40  242.1180   8.6757  218.8900  261.4400    0.0799    0.0559
32      35 2024-01-16 2024-03-05      35  195.6677  10.3287  180.7400  219.9100    0.5612   -0.6363
33      36 2024-03-06 2024-06-28      80  174.5276  10.1661  142.0500  197.8800   -0.7547    1.9696
34      37 2024-07-01 2024-07-22      15  246.9640  12.7315  209.8600  263.2600   -1.4922    2.3260
35      38 2024-07-23 2024-09-10      35  214.3086  11.4411  191.7600  246.3800    0.3742    0.2266
36      39 2024-09-11 2024-11-05      40  240.6265  15.7184  213.6500  269.1900   -0.1095   -1.2880
37      40 2024-11-06 2024-12-04      20  334.3785  18.4153  288.5300  357.9300   -0.9879    0.3082
38      42 2024-12-12 2024-12-26      10  444.1580  18.9671  418.1000  479.8600    0.3749   -0.9472
39      43 2024-12-27 2025-02-04      25  405.1672  13.5574  379.2800  431.6600    0.2049   -0.6805
40      44 2025-02-05 2025-02-26      15  344.8420  23.4425  290.8000  378.1700   -0.8515    0.0794
41      46 2025-03-06 2025-04-24      35  251.1791  17.3528  221.8600  288.1400    0.1965   -0.8057
42      47 2025-04-25 2025-05-08      10  282.9400   4.8321  275.3500  292.0300    0.0838   -0.6691
43      48 2025-05-09 2025-05-30      15  341.1187  15.5627  298.2600  362.8900   -1.2473    1.5813
44      49 2025-06-02 2025-08-29      63  323.2003  14.7991  284.7000  351.6700   -0.2681   -0.2613
ANOVA тест на равенство средних:
F-статистика: 1424.3180
P-значение: 0.0000
Заключение: Режимы значимо различаются

Анализ множественных структурных сдвигов представляет собой существенный шаг вперед в понимании динамики финансовых временных рядов. Использование различных алгоритмов детекции (Pelt, BottomUp, Window) позволяет получить надежную оценку количества и местоположения структурных изменений. Каждый алгоритм имеет свои преимущества: Pelt оптимален для детекции резких изменений, BottomUp лучше выявляет постепенные сдвиги, а Window подходит для локального анализа.

👉🏻  Сезонность временных рядов. В чем отличие аддитивной от мультипликативной?

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

Продвинутые методы: Тест Kapetanios-Shin-Snell для нелинейной стационарности

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

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

Тест Kapetanios-Shin-Snell (KSS) позволяет выявлять стационарность в данных, даже когда она проявляется через нелинейные зависимости, обходя ограничения классических линейных тестов, таких как ADF или KPSS, которые корректно работают только при линейной динамике. KSS использует модель экспоненциального авторегрессионного процесса (ESTAR), учитывающую возможные нелинейные корректировки к тренду, что делает его более чувствительным к стационарности в системах с нелинейными и асимметричными колебаниями, где линейные тесты часто дают ложные выводы о наличии единичного корня.

from scipy.optimize import minimize
from scipy.stats import chi2
def kss_test(series, lag_method='aic', max_lags=12):
"""
Тест Kapetanios-Shin-Snell для нелинейной стационарности
Тестирует нулевую гипотезу единичного корня против альтернативы 
стационарности вокруг smooth transition
"""
def logistic_smooth_transition(x, gamma, c):
"""Логистическая функция перехода"""
return 1 / (1 + np.exp(-gamma * (x - c)))
def kss_regression(y, gamma, c, max_lag):
"""Регрессия KSS теста"""
n = len(y)
# Создаем лагированные значения
y_lag = y[:-1]
dy = np.diff(y)
# Smooth transition функция
st = logistic_smooth_transition(y_lag[max_lag:], gamma, c)
# Нелинейная часть
nonlinear_part = y_lag[max_lag:] * st
# Подготавливаем данные для регрессии
Y = dy[max_lag:]
X = nonlinear_part
# Добавляем лаги если нужно
if max_lag > 0:
for i in range(1, max_lag + 1):
if len(dy) > max_lag + i:
X = np.column_stack([X, dy[max_lag-i:-i]])
X = X.reshape(-1, 1) if X.ndim == 1 else X
# OLS регрессия
try:
beta = np.linalg.lstsq(X, Y, rcond=None)[0]
residuals = Y - X @ beta
ssr = np.sum(residuals**2)
return ssr, beta[0]  # Возвращаем SSR и коэффициент при нелинейной части
except:
return np.inf, 0
# Определяем оптимальное количество лагов
if lag_method == 'aic':
best_lag = 1
best_aic = np.inf
for lag in range(0, min(max_lags + 1, len(series) // 4)):
try:
# Простая AR модель для AIC
if lag == 0:
continue
y_dep = series[lag:]
X_ar = np.column_stack([series[lag-i-1:-i-1] for i in range(lag)])
beta_ar = np.linalg.lstsq(X_ar, y_dep, rcond=None)[0]
residuals_ar = y_dep - X_ar @ beta_ar
ssr_ar = np.sum(residuals_ar**2)
aic = len(y_dep) * np.log(ssr_ar / len(y_dep)) + 2 * lag
if aic < best_aic:
best_aic = aic
best_lag = lag
except:
continue
else:
best_lag = lag_method
# Нормализуем ряд
y_norm = (series - np.mean(series)) / np.std(series)
# Сетка для поиска параметров
gamma_grid = [0.1, 1, 10, 100]
c_grid = np.percentile(y_norm, [10, 25, 50, 75, 90])
best_ssr = np.inf
best_params = None
best_t_stat = 0
for gamma in gamma_grid:
for c in c_grid:
try:
ssr, coef = kss_regression(y_norm, gamma, c, best_lag)
if ssr < best_ssr: best_ssr = ssr best_params = (gamma, c) # Вычисляем t-статистику для коэффициента # Упрощенная версия без полного вычисления стандартной ошибки if ssr > 0:
best_t_stat = abs(coef) / np.sqrt(ssr / (len(y_norm) - best_lag - 2))
except:
continue
# Критические значения (приблизительные, основаны на симуляциях)
critical_values = {
'10%': 2.82,
'5%': 3.17, 
'1%': 3.93
}
# P-значение (приблизительное)
p_value = 1 - chi2.cdf(best_t_stat**2, df=1) if best_t_stat > 0 else 1.0
return {
'statistic': best_t_stat,
'pvalue': p_value,
'critical_values': critical_values,
'parameters': best_params,
'lags': best_lag,
'conclusion': 'Нелинейно стационарен' if best_t_stat > critical_values['5%'] else 'Нестационарен'
}
# Применяем KSS тест к различным трансформациям
print("=== Тест KSS для нелинейной стационарности ===")
kss_prices = kss_test(extended_prices.values)
print(f"\nЦены TSLA:")
print(f"Статистика: {kss_prices['statistic']:.4f}")
print(f"P-значение: {kss_prices['pvalue']:.4f}")
print(f"Критическое значение (5%): {kss_prices['critical_values']['5%']:.4f}")
print(f"Заключение: {kss_prices['conclusion']}")
# Тест для доходностей
extended_returns = extended_prices.pct_change().dropna()
kss_returns = kss_test(extended_returns.values)
print(f"\nДоходности TSLA:")
print(f"Статистика: {kss_returns['statistic']:.4f}")
print(f"P-значение: {kss_returns['pvalue']:.4f}")
print(f"Заключение: {kss_returns['conclusion']}")
# Сравнение с линейными тестами
def comprehensive_nonlinear_analysis(series, name):
"""
Комплексное сравнение линейных и нелинейных тестов стационарности
"""
print(f"\n=== Комплексный анализ стационарности: {name} ===")
# Линейные тесты
adf_result = adfuller(series, autolag='AIC')
kpss_result = kpss(series, regression='c')
# Нелинейный тест
kss_result = kss_test(series)
# Сводная таблица результатов
results_summary = {
'ADF': {
'H0': 'Единичный корень',
'Статистика': adf_result[0],
'P-значение': adf_result[1],
'Заключение': 'Стационарен' if adf_result[1] < 0.05 else 'Нестационарен' }, 'KPSS': { 'H0': 'Стационарность', 'Статистика': kpss_result[0], 'P-значение': kpss_result[1], 'Заключение': 'Стационарен' if kpss_result[1] > 0.05 else 'Нестационарен'
},
'KSS': {
'H0': 'Единичный корень',
'Статистика': kss_result['statistic'],
'P-значение': kss_result['pvalue'],
'Заключение': kss_result['conclusion']
}
}
print("\nСравнение результатов тестов:")
print(f"{'Тест':<8} {'H0':<20} {'Статистика':<12} {'P-значение':<12} {'Заключение':<20}")
print("-" * 80)
for test_name, results in results_summary.items():
print(f"{test_name:<8} {results['H0']:<20} {results['Статистика']:<12.4f} "
f"{results['P-значение']:<12.4f} {results['Заключение']:<20}")
return results_summary
# Применяем комплексный анализ
linear_vs_nonlinear_prices = comprehensive_nonlinear_analysis(extended_prices.values, "Цены TSLA")
linear_vs_nonlinear_returns = comprehensive_nonlinear_analysis(extended_returns.values, "Доходности TSLA")
=== Тест KSS для нелинейной стационарности ===
Цены TSLA:
Статистика: 0.0000
P-значение: 1.0000
Критическое значение (5%): 3.1700
Заключение: Нестационарен
Доходности TSLA:
Статистика: 0.0000
P-значение: 1.0000
Заключение: Нестационарен
=== Комплексный анализ стационарности: Цены TSLA ===
Сравнение результатов тестов:
Тест     H0                   Статистика   P-значение   Заключение          
--------------------------------------------------------------------------------
ADF      Единичный корень     -2.7754      0.0619       Нестационарен       
KPSS     Стационарность       1.6772       0.0100       Нестационарен       
KSS      Единичный корень     0.0000       1.0000       Нестационарен       
=== Комплексный анализ стационарности: Доходности TSLA ===
Сравнение результатов тестов:
Тест     H0                   Статистика   P-значение   Заключение          
--------------------------------------------------------------------------------
ADF      Единичный корень     -37.8791     0.0000       Стационарен         
KPSS     Стационарность       0.4173       0.0697       Стационарен         
KSS      Единичный корень     0.0000       1.0000       Нестационарен 

Тест Kapetanios-Shin-Snell представляет собой мощный инструмент для выявления нелинейной стационарности в финансовых временных рядах. Основная идея заключается в тестировании единичного корня против альтернативы стационарности вокруг функции плавного перехода. Это позволяет выявить ситуации, когда ряд демонстрирует склонность возврата к среднему (mean reversion pattern), но с нелинейной скоростью возврата к равновесию.

👉🏻  Доверительная вероятность и уровень значимости в финансовом Data Science

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

Практические рекомендации по выбору тестов

Алгоритм выбора подходящего теста

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

import numpy as np
import pandas as pd
from statsmodels.tsa.stattools import kpss, adfuller
from arch.unitroot import PhillipsPerron
def stationarity_test_selection_framework(series, series_info=None):
"""
Фреймворк для выбора подходящих тестов стационарности
"""
if series_info is None:
series_info = {}
recommendations = {
'primary_tests': [],
'secondary_tests': [],
'considerations': [],
'preprocessing': []
}
# Базовая статистика ряда
n = len(series)
cv = np.std(series) / np.abs(np.mean(series)) if np.mean(series) != 0 else np.inf
# Тест на нормальность
from scipy.stats import jarque_bera
jb_stat, jb_pvalue = jarque_bera(series)
is_normal = jb_pvalue > 0.05
# Тест на автокорреляцию
from statsmodels.stats.diagnostic import acorr_ljungbox
try:
lb_result = acorr_ljungbox(series, lags=min(10, n//4), return_df=True)
has_autocorr = any(lb_result['lb_pvalue'] < 0.05) except: has_autocorr = False # Визуальная оценка тренда x = np.arange(len(series)) try: # Убеждаемся что оба массива одинаковой длины if len(x) == len(series): trend_coef = np.corrcoef(x, series)[0, 1] has_trend = abs(trend_coef) > 0.1
else:
has_trend = False
except:
has_trend = False
# 1. Размер выборки
if n < 50:
recommendations['considerations'].append("Малая выборка: результаты тестов могут быть ненадежными")
recommendations['primary_tests'].append("KPSS (более мощный для малых выборок)")
elif n < 200: recommendations['primary_tests'].extend(["ADF", "KPSS"]) else: recommendations['primary_tests'].extend(["ADF", "KPSS", "Phillips-Perron"]) # 2. Наличие тренда if has_trend: recommendations['considerations'].append("Обнаружен возможный тренд") recommendations['secondary_tests'].append("Zivot-Andrews (структурные сдвиги)") recommendations['preprocessing'].append("Рассмотреть детрендирование") # 3. Автокорреляция if has_autocorr: recommendations['considerations'].append("Значимая автокорреляция") recommendations['primary_tests'].append("Phillips-Perron (робастен к автокорреляции)") # 4. Нелинейность (простая проверка) try: from statsmodels.tsa.ar_model import AutoReg if n > 10:  # Минимальный размер для AR модели
ar_model = AutoReg(series, lags=1).fit()
ar_residuals = ar_model.resid
# Простой тест на нелинейность через остатки
if len(ar_residuals) > 2:
squared_residuals = ar_residuals ** 2
if len(ar_residuals) > 1 and len(squared_residuals) > 1:
correlation_nonlinear = np.corrcoef(ar_residuals[:-1], squared_residuals[1:])[0, 1]
if abs(correlation_nonlinear) > 0.1:
recommendations['secondary_tests'].append("KSS (нелинейная стационарность)")
recommendations['considerations'].append("Возможная нелинейность")
except:
pass
# 5. Волатильность
if cv > 1:
recommendations['considerations'].append("Высокая волатильность")
recommendations['preprocessing'].append("Рассмотреть логарифмирование")
# 6. Тип данных
data_type = series_info.get('type', 'unknown')
if data_type == 'prices':
recommendations['preprocessing'].append("Перейти к доходностям")
recommendations['primary_tests'].append("Тесты для уровней и первых разностей")
elif data_type == 'exchange_rates':
recommendations['secondary_tests'].append("KSS (пороговые эффекты)")
elif data_type == 'volatility':
recommendations['secondary_tests'].append("Режимные модели")
return recommendations
def execute_recommended_tests(series, recommendations, name):
"""
Выполнение рекомендованных тестов
"""
print(f"\n=== Выполнение рекомендованных тестов для {name} ===")
results = {}
# Предобработка
if recommendations['preprocessing']:
print("\nРекомендации по предобработке:")
for rec in recommendations['preprocessing']:
print(f"- {rec}")
# Основные тесты
print(f"\nОсновные тесты: {', '.join(recommendations['primary_tests'])}")
if "ADF" in recommendations['primary_tests']:
try:
adf_result = adfuller(series, autolag='AIC')
results['ADF'] = {
'statistic': adf_result[0],
'pvalue': adf_result[1],
'conclusion': 'Стационарен' if adf_result[1] < 0.05 else 'Нестационарен' } print(f"ADF: {results['ADF']['conclusion']} (p={adf_result[1]:.4f})") except Exception as e: print(f"Ошибка в ADF тесте: {e}") if "KPSS" in recommendations['primary_tests']: try: kpss_result = kpss(series, regression='c') results['KPSS'] = { 'statistic': kpss_result[0], 'pvalue': kpss_result[1], 'conclusion': 'Стационарен' if kpss_result[1] > 0.05 else 'Нестационарен'
}
print(f"KPSS: {results['KPSS']['conclusion']} (p={kpss_result[1]:.4f})")
except Exception as e:
print(f"Ошибка в KPSS тесте: {e}")
if "Phillips-Perron" in recommendations['primary_tests']:
try:
pp_result = PhillipsPerron(series, lags=5)
results['PP'] = {
'statistic': pp_result.stat,
'pvalue': pp_result.pvalue,
'conclusion': 'Стационарен' if pp_result.pvalue < 0.05 else 'Нестационарен'
}
print(f"Phillips-Perron: {results['PP']['conclusion']} (p={pp_result.pvalue:.4f})")
except Exception as e:
print(f"Ошибка в Phillips-Perron тесте: {e}")
# Дополнительные тесты
if recommendations['secondary_tests']:
print(f"\nДополнительные тесты: {', '.join(recommendations['secondary_tests'])}")
if "KSS (нелинейная стационарность)" in recommendations['secondary_tests']:
try:
# Здесь должна быть функция kss_test, если она определена
print("KSS тест пропущен (функция не определена в текущем контексте)")
except Exception as e:
print(f"Ошибка в KSS тесте: {e}")
# Итоговое заключение
print(f"\nИтоговое заключение:")
if 'ADF' in results and 'KPSS' in results:
adf_stationary = results['ADF']['conclusion'] == 'Стационарен'
kpss_stationary = results['KPSS']['conclusion'] == 'Стационарен'
if adf_stationary and kpss_stationary:
print("✓ Ряд стационарен (согласие основных тестов)")
elif not adf_stationary and not kpss_stationary:
print("✗ Ряд нестационарен (согласие основных тестов)")
else:
print("? Противоречивые результаты - требуется дополнительный анализ")
return results
# Создаем итоговую матрицу решений
def create_decision_matrix():
"""
Создание матрицы решений для выбора тестов стационарности
"""
decision_matrix = pd.DataFrame({
'Характеристика данных': [
'Малая выборка (n < 50)',
'Средняя выборка (50 ≤ n < 200)',
'Большая выборка (n ≥ 200)',
'Наличие тренда',
'Высокая автокорреляция',
'Подозрение на структурные сдвиги',
'Нелинейные зависимости',
'Высокая волатильность',
'Валютные курсы',
'Ценовые данные',
'Данные по волатильности'
],
'Рекомендуемые тесты': [
'KPSS, визуальный анализ',
'ADF + KPSS',
'ADF + KPSS + Phillips-Perron',
'Zivot-Andrews, детрендирование',
'Phillips-Perron',
'Zivot-Andrews, множественные сдвиги',
'KSS, пороговые модели',
'Логарифмирование, GARCH тесты',
'KSS, пороговые тесты',
'Тесты для уровней и разностей',
'Режимные модели, GARCH'
],
'Приоритет': [
'Высокий',
'Высокий',
'Высокий',
'Средний',
'Высокий',
'Средний',
'Низкий',
'Средний',
'Средний',
'Высокий',
'Низкий'
]
})
print("\n=== Матрица решений для выбора тестов стационарности ===")
print(decision_matrix.to_string(index=False))
return decision_matrix
# Предполагаем, что extended_prices и extended_returns уже определены
# Создаем доходности если их нет
if 'extended_returns' not in locals():
extended_returns = extended_prices.pct_change().dropna()
# Применяем фреймворк к различным типам данных
price_recommendations = stationarity_test_selection_framework(
extended_prices.values,
{'type': 'prices'}
)
returns_recommendations = stationarity_test_selection_framework(
extended_returns.values,
{'type': 'returns'}
)
print("=== Рекомендации для цен TSLA ===")
for key, values in price_recommendations.items():
print(f"{key}: {values}")
print("\n=== Рекомендации для доходностей TSLA ===")
for key, values in returns_recommendations.items():
print(f"{key}: {values}")
# Выполняем рекомендованные тесты
price_test_results = execute_recommended_tests(
extended_prices.values,
price_recommendations,
"Цены TSLA"
)
returns_test_results = execute_recommended_tests(
extended_returns.values,
returns_recommendations,
"Доходности TSLA"
)
# Создаем матрицу решений
decision_matrix = create_decision_matrix()
=== Рекомендации для цен TSLA ===
primary_tests: ['ADF', 'KPSS', 'Phillips-Perron', 'Phillips-Perron (робастен к автокорреляции)', 'Тесты для уровней и первых разностей']
secondary_tests: []
considerations: ['Значимая автокорреляция']
preprocessing: ['Перейти к доходностям']
=== Рекомендации для доходностей TSLA ===
primary_tests: ['ADF', 'KPSS', 'Phillips-Perron']
secondary_tests: []
considerations: ['Высокая волатильность']
preprocessing: ['Рассмотреть логарифмирование']
=== Выполнение рекомендованных тестов для Цены TSLA ===
Рекомендации по предобработке:
- Перейти к доходностям
Основные тесты: ADF, KPSS, Phillips-Perron, Phillips-Perron (робастен к автокорреляции), Тесты для уровней и первых разностей
ADF: Нестационарен (p=0.0619)
KPSS: Нестационарен (p=0.0100)
Phillips-Perron: Нестационарен (p=0.1222)
Итоговое заключение:
✗ Ряд нестационарен (согласие основных тестов)
=== Выполнение рекомендованных тестов для Доходности TSLA ===
Рекомендации по предобработке:
- Рассмотреть логарифмирование
Основные тесты: ADF, KPSS, Phillips-Perron
ADF: Стационарен (p=0.0000)
KPSS: Стационарен (p=0.0697)
Phillips-Perron: Стационарен (p=0.0000)
Итоговое заключение:
✓ Ряд стационарен (согласие основных тестов)
=== Матрица решений для выбора тестов стационарности ===
Характеристика данных                 Рекомендуемые тесты Приоритет
Малая выборка (n < 50)             KPSS, визуальный анализ   Высокий
Средняя выборка (50 ≤ n < 200)                          ADF + KPSS   Высокий
Большая выборка (n ≥ 200)        ADF + KPSS + Phillips-Perron   Высокий
Наличие тренда      Zivot-Andrews, детрендирование   Средний
Высокая автокорреляция                     Phillips-Perron   Высокий
Подозрение на структурные сдвиги Zivot-Andrews, множественные сдвиги   Средний
Нелинейные зависимости               KSS, пороговые модели    Низкий
Высокая волатильность       Логарифмирование, GARCH тесты   Средний
Валютные курсы                KSS, пороговые тесты   Средний
Ценовые данные       Тесты для уровней и разностей   Высокий
Данные по волатильности              Режимные модели, GARCH    Низкий

Разработанный фреймворк автоматизирует процесс выбора подходящих тестов стационарности на основе характеристик данных. Ключевое преимущество такого подхода заключается в систематическом учете особенностей временного ряда и автоматической генерации рекомендаций по предобработке и тестированию.

👉🏻  Библиотека sktime для анализа временных рядов

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

Интерпретация противоречивых результатов

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

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.stattools import kpss, adfuller
from arch.unitroot import PhillipsPerron
from scipy import stats
from scipy.signal import periodogram
from statsmodels.tsa.stattools import acf
def handle_conflicting_results(series, name, additional_analysis=True):
"""
Анализ и интерпретация противоречивых результатов тестов стационарности
"""
print(f"\n=== Анализ противоречивых результатов для {name} ===")
# Убеждаемся что series - это 1D массив
if hasattr(series, 'values'):
series = series.values
series = np.array(series).flatten()
# Стандартные тесты
adf_result = adfuller(series, autolag='AIC')
kpss_result = kpss(series, regression='c')
adf_stationary = adf_result[1] < 0.05 kpss_stationary = kpss_result[1] > 0.05
print(f"ADF тест: {'Стационарен' if adf_stationary else 'Нестационарен'} (p={adf_result[1]:.4f})")
print(f"KPSS тест: {'Стационарен' if kpss_stationary else 'Нестационарен'} (p={kpss_result[1]:.4f})")
if adf_stationary == kpss_stationary:
print("✓ Тесты согласуются")
return "Согласие"
print("⚠ Противоречивые результаты - требуется дополнительный анализ")
if not additional_analysis:
return "Противоречие"
# Дополнительный анализ
analysis_results = {}
# 1. Phillips-Perron как третий арбитр
try:
pp_result = PhillipsPerron(series, lags=5)
pp_stationary = pp_result.pvalue < 0.05
analysis_results['phillips_perron'] = pp_stationary
print(f"\nPhillips-Perron (арбитр): {'Стационарен' if pp_stationary else 'Нестационарен'}")
except Exception as e:
analysis_results['phillips_perron'] = None
print(f"Ошибка в Phillips-Perron: {e}")
# 2. Анализ с различным количеством лагов для ADF
print(f"\nАнализ чувствительности ADF к выбору лагов:")
adf_results_by_lags = {}
for lags in [1, 2, 3, 5, 8, 12]:
try:
if lags < len(series) // 4:
adf_lag = adfuller(series, maxlag=lags, autolag=None)
adf_results_by_lags[lags] = adf_lag[1] < 0.05
print(f"Лаги {lags}: {'Стационарен' if adf_lag[1] < 0.05 else 'Нестационарен'} (p={adf_lag[1]:.4f})") except Exception as e: continue # 3. Анализ различных трендовых спецификаций для KPSS try: print(f"\nАнализ различных спецификаций KPSS:") kpss_trend = kpss(series, regression='ct') kpss_trend_stationary = kpss_trend[1] > 0.05
print(f"KPSS с трендом: {'Стационарен' if kpss_trend_stationary else 'Нестационарен'} (p={kpss_trend[1]:.4f})")
except Exception as e:
print(f"Ошибка в KPSS с трендом: {e}")
# 4. Проверка на структурные сдвиги (используем нашу собственную реализацию)
try:
# Здесь можно использовать нашу функцию
print("Используйте как допфункцию Zivot-Andrews или реализацию Perron из предыдущего кода")
analysis_results['zivot_andrews'] = None
except Exception as e:
analysis_results['zivot_andrews'] = None
print(f"Ошибка в Zivot-Andrews: {e}")
# 5. Визуальный анализ
try:
rolling_mean = pd.Series(series).rolling(window=max(20, len(series)//10)).mean()
rolling_std = pd.Series(series).rolling(window=max(20, len(series)//10)).std()
mean_stability = np.std(rolling_mean.dropna()) / np.mean(rolling_mean.dropna()) if np.mean(rolling_mean.dropna()) != 0 else np.inf
std_stability = np.std(rolling_std.dropna()) / np.mean(rolling_std.dropna()) if np.mean(rolling_std.dropna()) != 0 else np.inf
print(f"\nВизуальный анализ:")
print(f"Стабильность среднего: {'Высокая' if mean_stability < 0.1 else 'Средняя' if mean_stability < 0.3 else 'Низкая'}")
print(f"Стабильность дисперсии: {'Высокая' if std_stability < 0.1 else 'Средняя' if std_stability < 0.3 else 'Низкая'}") except Exception as e: print(f"Ошибка в визуальном анализе: {e}") # Итоговые рекомендации print(f"\n=== Рекомендации по разрешению противоречий ===") if adf_stationary and not kpss_stationary: print("Случай: ADF указывает на стационарность, KPSS - на нестационарность") print("Возможные причины:") print("- Структурные сдвиги в среднем или тренде") print("- Медленно затухающие автокорреляции") print("- Нелинейная mean reversion") if analysis_results.get('zivot_andrews'): print("✓ Zivot-Andrews подтверждает стационарность с учетом сдвигов") return "Стационарен с структурными сдвигами" else: print("? Рекомендуется проверка на структурные сдвиги") elif not adf_stationary and kpss_stationary: print("Случай: ADF указывает на нестационарность, KPSS - на стационарность") print("Возможные причины:") print("- Низкая мощность ADF (близость к единичному корню)") print("- Неправильный выбор количества лагов") print("- Наличие moving average компонент") if analysis_results.get('phillips_perron'): print("✓ Phillips-Perron подтверждает стационарность") return "Стационарен (PP подтверждает)" return "Требует дополнительного анализа" def visualize_stationarity_diagnostics(series, name): """ Визуализация для диагностики проблем со стационарностью """ # Убеждаемся что series - это 1D массив if hasattr(series, 'values'): series = series.values series = np.array(series).flatten() fig, axes = plt.subplots(2, 3, figsize=(18, 10)) fig.suptitle(f'Диагностика стационарности: {name}', fontsize=16, fontweight='bold') try: # Исходный ряд axes[0,0].plot(series, color='black', linewidth=1) axes[0,0].set_title('Исходный временной ряд', fontweight='bold') axes[0,0].grid(True, alpha=0.3) axes[0,0].set_ylabel('Значение') except Exception as e: axes[0,0].text(0.5, 0.5, f'Ошибка: {e}', transform=axes[0,0].transAxes, ha='center') try: # Скользящее среднее и стандартное отклонение series_pd = pd.Series(series) window = max(20, len(series)//10) rolling_mean = series_pd.rolling(window=window).mean() rolling_std = series_pd.rolling(window=window).std() axes[0,1].plot(rolling_mean, color='red', label='Скользящее среднее', linewidth=2) axes[0,1].plot(rolling_std, color='blue', label='Скользящее СКО', linewidth=2) axes[0,1].set_title('Скользящие характеристики', fontweight='bold') axes[0,1].legend() axes[0,1].grid(True, alpha=0.3) except Exception as e: axes[0,1].text(0.5, 0.5, f'Ошибка: {e}', transform=axes[0,1].transAxes, ha='center') try: # Автокорреляционная функция max_lags = min(40, len(series)//4) if max_lags > 1:
autocorr = acf(series, nlags=max_lags)
axes[0,2].plot(autocorr, color='darkgreen', marker='o', markersize=3)
axes[0,2].axhline(y=0, color='black', linestyle='-', alpha=0.3)
axes[0,2].axhline(y=1.96/np.sqrt(len(series)), color='red', linestyle='--', alpha=0.5, label='95% доверительный интервал')
axes[0,2].axhline(y=-1.96/np.sqrt(len(series)), color='red', linestyle='--', alpha=0.5)
axes[0,2].set_title('Автокорреляционная функция', fontweight='bold')
axes[0,2].grid(True, alpha=0.3)
axes[0,2].legend()
else:
axes[0,2].text(0.5, 0.5, 'Недостаточно данных для ACF', transform=axes[0,2].transAxes, ha='center')
except Exception as e:
axes[0,2].text(0.5, 0.5, f'Ошибка ACF: {e}', transform=axes[0,2].transAxes, ha='center')
try:
# Первые разности
diff_series = np.diff(series)
axes[1,0].plot(diff_series, color='gray', linewidth=0.8)
axes[1,0].set_title('Первые разности', fontweight='bold')
axes[1,0].grid(True, alpha=0.3)
axes[1,0].set_ylabel('Δ Значение')
except Exception as e:
axes[1,0].text(0.5, 0.5, f'Ошибка: {e}', transform=axes[1,0].transAxes, ha='center')
try:
# QQ-plot для проверки нормальности
stats.probplot(series, dist="norm", plot=axes[1,1])
axes[1,1].set_title('Q-Q plot (нормальность)', fontweight='bold')
axes[1,1].grid(True, alpha=0.3)
except Exception as e:
axes[1,1].text(0.5, 0.5, f'Ошибка QQ-plot: {e}', transform=axes[1,1].transAxes, ha='center')
try:
# Спектральная плотность
if len(series) > 10:
freqs, psd = periodogram(series)
if len(freqs) > 1:
axes[1,2].semilogy(freqs[1:], psd[1:], color='purple')
axes[1,2].set_title('Спектральная плотность', fontweight='bold')
axes[1,2].set_xlabel('Частота')
axes[1,2].set_ylabel('Мощность (лог)')
axes[1,2].grid(True, alpha=0.3)
else:
axes[1,2].text(0.5, 0.5, 'Недостаточно данных для спектра', transform=axes[1,2].transAxes, ha='center')
else:
axes[1,2].text(0.5, 0.5, 'Недостаточно данных', transform=axes[1,2].transAxes, ha='center')
except Exception as e:
axes[1,2].text(0.5, 0.5, f'Ошибка спектра: {e}', transform=axes[1,2].transAxes, ha='center')
plt.tight_layout()
plt.show()
# Предполагаем что extended_prices и extended_returns уже загружены
# Если нет, то загружаем данные
try:
# Проверяем есть ли данные
test_data = extended_prices
except NameError:
# Если данных нет, загружаем заново
import yfinance as yf
extended_data = yf.download("TSLA", start="2020-01-01", end="2025-09-01")
if isinstance(extended_data.columns, pd.MultiIndex):
extended_prices = extended_data['Close'].iloc[:, 0].dropna()
else:
extended_prices = extended_data['Close'].dropna()
extended_returns = extended_prices.pct_change().dropna()
# Применяем анализ противоречий
conflict_analysis_prices = handle_conflicting_results(extended_prices.values, "Цены TSLA")
conflict_analysis_returns = handle_conflicting_results(extended_returns.values, "Доходности TSLA")
# Создаем диагностические графики
print("\nСоздание диагностических графиков...")
visualize_stationarity_diagnostics(extended_prices.values, "Цены TSLA")
visualize_stationarity_diagnostics(extended_returns.values, "Доходности TSLA")
=== Анализ противоречивых результатов для Цены TSLA ===
ADF тест: Нестационарен (p=0.0619)
KPSS тест: Нестационарен (p=0.0100)
✓ Тесты согласуются
=== Анализ противоречивых результатов для Доходности TSLA ===
ADF тест: Стационарен (p=0.0000)
KPSS тест: Стационарен (p=0.0697)
✓ Тесты согласуются

Диагностика стационарности цен TSLA показывает нестационарный процесс с трендом, персистентной автокорреляцией и концентрацией спектральной энергии на низких частотах

Рис. 4: Диагностика стационарности цен TSLA показывает нестационарный процесс с трендом, персистентной автокорреляцией и концентрацией спектральной энергии на низких частотах

Диагностические характеристики доходностей TSLA демонстрируют стационарный процесс с быстро затухающей автокорреляцией, ненормальным распределением с тяжелыми хвостами и плоской спектральной плотностью

Рис. 5: Диагностические характеристики доходностей TSLA демонстрируют стационарный процесс с быстро затухающей автокорреляцией, ненормальным распределением с тяжелыми хвостами и плоской спектральной плотностью

Анализ противоречивых результатов требует систематического подхода и понимания ограничений каждого теста. В моей практике наиболее часто встречающийся случай — когда ADF показывает стационарность, а KPSS нестационарность — обычно указывает на наличие структурных сдвигов или медленно затухающих автокорреляций. В таких ситуациях применение теста Перрона часто разрешает противоречие.

👉🏻  Анализ акций Tesla с помощью Python

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

Заключение

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

Ключевые выводы из проведенного анализа включают несколько важных аспектов:

  • Тест KPSS с его обращенной логикой тестирования обеспечивает более консервативный подход к принятию стационарности и особенно ценен при работе с короткими временными рядами;
  • Тест Phillips-Perron демонстрирует превосходную робастность к автокорреляции и гетероскедастичности, что делает его незаменимым для высокочастотных финансовых данных;
  • Тесты структурных сдвигов Зивота-Эндрюса и Перрона позволяют различать истинную нестационарность от временных изменений в параметрах процесса, что имеет критическое значение для долгосрочных стратегий;
  • Нелинейные тесты, такие как KSS, открывают новые возможности для анализа сложных финансовых инструментов с пороговыми эффектами и режимными переключениями. Особенно это актуально для валютных рынков и товарных активов, где классические линейные модели часто оказываются неадекватными.

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

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

👉🏻  Алгоритмы расчета точек безубыточности стратегий