Стационарность временного ряда означает постоянство его статистических свойств во времени. Это не академическая абстракция — от корректного определения стационарности зависит выбор модели прогнозирования, риск-менеджмент и конечная прибыльность стратегии. Нестационарные ряды могут привести к ложным корреляциям, переобучению моделей и катастрофическим потерям в реальной торговле.
Проблемы классического теста Дики-Фуллера
Расширенный тест Дики-Фуллера (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")

Рис. 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

Рис. 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
Заключение: Стационарен в данном режиме

Рис. 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), но с нелинейной скоростью возврата к равновесию.
Практическая ценность нелинейных тестов особенно высока при анализе валютных курсов, товарных цен и волатильности, где часто наблюдаются пороговые эффекты и режимные переключения. Например, валютный курс может демонстрировать различную скорость возврата к паритету в зависимости от величины отклонения от равновесного уровня.
Практические рекомендации по выбору тестов
Алгоритм выбора подходящего теста
Выбор подходящего теста стационарности зависит от характеристик данных, целей анализа и предполагаемых свойств временного ряда. На основе многолетнего опыта работы с финансовыми данными я разработал практический алгоритм, который помогает определить оптимальную батарею тестов для конкретной ситуации.
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 Низкий
Разработанный фреймворк автоматизирует процесс выбора подходящих тестов стационарности на основе характеристик данных. Ключевое преимущество такого подхода заключается в систематическом учете особенностей временного ряда и автоматической генерации рекомендаций по предобработке и тестированию.
Матрица решений представляет собой практический инструмент, который можно использовать в ежедневной работе для быстрого определения стратегии анализа. Важно отметить, что рекомендации носят направляющий характер и должны дополняться экспертным суждением, особенно при работе с нестандартными данными или в условиях рыночных кризисов.
Интерпретация противоречивых результатов
Одна из наиболее сложных ситуаций в практике тестирования стационарности возникает при получении противоречивых результатов от различных тестов. Например, 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)
✓ Тесты согласуются

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

Рис. 5: Диагностические характеристики доходностей TSLA демонстрируют стационарный процесс с быстро затухающей автокорреляцией, ненормальным распределением с тяжелыми хвостами и плоской спектральной плотностью
Анализ противоречивых результатов требует систематического подхода и понимания ограничений каждого теста. В моей практике наиболее часто встречающийся случай — когда ADF показывает стационарность, а KPSS нестационарность — обычно указывает на наличие структурных сдвигов или медленно затухающих автокорреляций. В таких ситуациях применение теста Перрона часто разрешает противоречие.
Диагностические графики играют важную роль в интерпретации результатов. Анализ скользящих характеристик помогает выявить изменения в среднем и дисперсии, автокорреляционная функция показывает структуру зависимостей, а спектральная плотность может выявить циклические компоненты, которые могут влиять на результаты тестов.
Заключение
Расширенные методы тестирования стационарности представляют собой мощный арсенал инструментов для глубокого анализа временных рядов в количественных финансах. За годы практической работы я убедился, что правильное применение этих методов может кардинально изменить результативность торговых стратегий и качество прогнозных моделей.
Ключевые выводы из проведенного анализа включают несколько важных аспектов:
- Тест KPSS с его обращенной логикой тестирования обеспечивает более консервативный подход к принятию стационарности и особенно ценен при работе с короткими временными рядами;
- Тест Phillips-Perron демонстрирует превосходную робастность к автокорреляции и гетероскедастичности, что делает его незаменимым для высокочастотных финансовых данных;
- Тесты структурных сдвигов Зивота-Эндрюса и Перрона позволяют различать истинную нестационарность от временных изменений в параметрах процесса, что имеет критическое значение для долгосрочных стратегий;
- Нелинейные тесты, такие как KSS, открывают новые возможности для анализа сложных финансовых инструментов с пороговыми эффектами и режимными переключениями. Особенно это актуально для валютных рынков и товарных активов, где классические линейные модели часто оказываются неадекватными.
Практическое применение этих методов требует системного подхода с использованием батареи тестов и обязательным анализом противоречивых результатов. Разработанный фреймворк выбора тестов позволяет автоматизировать этот процесс и обеспечить консистентность анализа при работе с большими портфелями активов.
Важно понимать, что тестирование стационарности — это не самоцель, а основа для построения надежных количественных моделей. Правильное определение стационарности влияет на выбор методов предобработки данных, спецификацию прогнозных моделей и оценку рисков торговых стратегий.