Phase Space Reconstruction (Реконструкция Фазового Пространства)
Реконструкция фазового пространства — это фундаментальный метод в теории хаоса и анализе временных рядов, который позволяет преобразовать одномерный временной ряд (например, цены акций) в многомерное пространство состояний. Это основано на теореме Такенса (Takens’ Embedding Theorem), которая утверждает, что для детерминированных систем можно восстановить топологию аттрактора (притягивающего множества) системы из одной наблюдаемой переменной, используя задержки (lags). В финансовых рынках это полезно для выявления скрытых паттернов, хаотичности и прогнозирования, поскольку цены часто проявляют нелинейную динамику.
Основные Принципы и Математика
Временной ряд: Пусть у нас есть последовательность
x(t) = \{x_1, x_2, \dots, x_N\} , где t — время.
Вложение (Embedding): Преобразуем ряд в векторы состояний в m-мерном пространстве:
vec{y}(t) = (x(t), x(t + \tau), x(t + 2\tau), \dots, x(t + (m-1)\tau))
где:
- m — размерность вложения (embedding dimension), обычно 2–5 для визуализации.
- \tau — задержка (time delay), выбирается так, чтобы компоненты были независимыми, но информативными (часто по mutual information или autocorrelation).
Выбор параметров:
- \tau : Минимизирует автокорреляцию или использует метод ложных ближайших соседей (False Nearest Neighbors, FNN) для оптимальности.
- m : Достаточно большая, чтобы развернуть аттрактор, но не слишком, чтобы избежать шума (метод FNN или размерность корреляции).
Применение в финансах: Для акций (как AAPL) сначала применяют фильтрацию (low-pass filter, e.g., Butterworth), чтобы удалить шум, затем строят фазовое пространство для выявления циклов или хаоса. Если аттрактор компактный — система предсказуемая; если странный (strange attractor) — хаотичная.
В хаотических системах (как Lorenz или Logistic map) реконструкция раскрывает геометрию, которую не видно в исходном ряде. Например, для Logistic map x_{n+1} = r x_n (1 - x_n) с r > 3.5 , embedding показывает хаотический аттрактор.
Готовые Решения и Библиотеки
- Python библиотеки:
nolds (для нелинейного анализа, включая embedding).
PyEMD или scipy для вспомогательных функций.
tsdss (PyPI): Поддерживает phase space reconstruction в nonlinear analysis.
- GitHub репозитории: frizchar/phase-space-reconstruction-of-financial-time-series (для финансов), FilippoMB/python-time-series-handbook (ноутбуки по time series).
- Инструменты: NoLiTiA (open-source toolbox) с скриптами для embedding: укажите
cfg.dim (dimension) и cfg.tau (delay).
Примеры Кода
Из GitHub frizchar/phase-space-reconstruction-of-financial-time-series (для финансовых рядов, e.g., AAPL):
Хотя прямой код не извлечен, описание алгоритма:
- Скачайте данные с yfinance.
- Примените Butterworth low-pass filter (scipy.signal.butter).
- Постройте 3D пространство: векторы (x_i, x{i+τ}, x{i+2τ}).
Примерный код на основе описания (адаптированный):
import yfinance as yf
import numpy as np
from scipy.signal import butter, filtfilt
import matplotlib.pyplot as plt
# Параметры
TICKER = 'AAPL'
START = '2020-01-01'
END = '2025-08-22'
CUTOFF_FREQ = 0.1 # Нормализованная частота
SAMPLE_RATE = 1.0 # Для ежедневных данных
ORDER = 4 # Порядок фильтра
TAU = 1 # Задержка
DIM = 3 # Размерность
# Скачивание данных
data = yf.download(TICKER, start=START, end=END)['Close'].values
# Low-pass filter
b, a = butter(ORDER, CUTOFF_FREQ, btype='low', analog=False, fs=SAMPLE_RATE)
filtered = filtfilt(b, a, data)
# Реконструкция фазового пространства
N = len(filtered)
embedded = np.array([filtered[i:N - (DIM-1)*TAU:TAU] for i in range(DIM)]).T
# Визуализация 3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(embedded[:,0], embedded[:,1], embedded[:,2])
plt.show()
Это строит 3D фазовое пространство после фильтрации.
Из блога coledie.com/DelayCoordinateEmbeddings (для delay coordinate embeddings):
Полный код для 2D/3D реконструкции из CSV-файла (e.g., Hénon map):
"""
Python 3, requires the library matplotlib and a csv file with evenly sampled data.
"""
FILENAME = 'henon_a14b03.csv'
COLUMN = 0 ## Which column of csv file to use, 0 means leftmost.
POINTS = -1 ## Number of points to use, more can be slower to render.
## -1 if all(but last).
E_DIMENSION = 2 ## Number of dimensions in embedding space -- 2 or 3.
TAU = 1 ## Delay, integer
import csv
import matplotlib.pyplot as plt ## pip install matplotlib
from mpl_toolkits.mplot3d import Axes3D
if __name__ == '__main__':
## Read Data
with open(FILENAME, 'r') as file:
time_series = [float(row[COLUMN]) for row in csv.reader(file)][:POINTS]
## Process Data
if E_DIMENSION == 2:
delay_coordinates = [
time_series[:-TAU if TAU else len(time_series)], # t-T
time_series[TAU:] # t
]
elif E_DIMENSION == 3:
delay_coordinates = [
time_series[TAU:-TAU if TAU else len(time_series)], # t-T
time_series[2 * TAU:], # t
time_series[:-2 * TAU if TAU else len(time_series)] # t-2T
]
else:
raise ValueError(f"Invalid Embedding Dimension, '{E_DIMENSION}' not in [2, 3]!")
## Visualize Embedding
fig = plt.figure()
# Raw time series data
ax = fig.add_subplot(211)
ax.scatter(range(len(time_series[:100])), time_series[:100])
ax.set_title('Discrete Time Series')
ax.set_xlabel('t')
ax.set_ylabel('x(t)')
# Reconstruction space
ax = fig.add_subplot(212, projection='3d' if E_DIMENSION == 3 else None)
ax.scatter(*delay_coordinates)
ax.set_title(f'Embedding, Tau={TAU}')
ax.set_xlabel(f'x(t-{TAU})')
ax.set_ylabel('x(t)')
if E_DIMENSION == 3:
ax.set_zlabel(f'x(t-{2*TAU})')
plt.show()
Это визуализирует embedding для Logistic или Hénon map.
Простой пример из SciPy (адаптированный из предыдущих ответов):
import numpy as np
from scipy.spatial.distance import pdist, squareform
def embed_series(series, dim, tau):
"""Реконструкция фазового пространства с задержкой tau и размерностью dim."""
N = len(series) - (dim - 1) * tau
embedded = np.empty((N, dim))
for i in range(N):
embedded[i] = series[i:i + dim * tau:tau]
return embedded
# Пример: prices — массив цен
prices = np.random.rand(1000) # Замените на реальные данные
embedded = embed_series(prices, dim=3, tau=1)
print(embedded.shape) # (N-2*tau, 3)
Lyapunov Exponents (Экспоненты Ляпунова)
Экспонента Ляпунова (LE) измеряет скорость расхождения ближайших траекторий в фазовом пространстве, указывая на хаотичность: λ > 0 — хаос, λ = 0 — периодичность, λ < 0 — устойчивость. В финансах LE помогает оценить предсказуемость рынка: положительная — рынок хаотичен, чувствителен к изменениям.
Основные Принципы и Математика
- Определение: Для двух близких точек в фазовом пространстве расстояние d(0) ~ ε (малое), после времени t: d(t) ≈ d(0) e^{λ t}. Тогда λ = (1/t) ln(d(t)/d(0)).
- Алгоритм для временных рядов (Rosenstein или Wolf):
- Реконструировать фазовое пространство.
- Найти ближайших соседей (false nearest neighbors избегают).
- Вычислить среднее ln(расстояния) по эволюции.
- Наклон линейной аппроксимации — LE.
- В финансах: Для лог-доходностей цен; отрицательная LE — стабильный тренд (buy), положительная — хаос (sell).
Готовые Решения и Библиотеки
- nolds: Функции lyap_r (Rosenstein) и lyap_e (Eckmann-Ruelle).
- GitHub: ThomasSavary08/Lyapynov (для CLV и adjoint).
- PyTorch примеры для NN, но для рядов — nolds.
Примеры Кода
Из блога abhranil.net (полный код для LE из ряда):
from math import log
def d(series,i,j):
return abs(series[i]-series[j])
f=open('timeseries.txt', 'r')
series=[float(i) for i in f.read().split()]
f.close()
N=len(series)
eps=input('Initial diameter bound: ')
dlist=[[] for i in range(N)]
n=0 #number of nearby pairs found
for i in range(N):
for j in range(i+1,N):
if d(series,i,j) < eps:
n+=1
print n
for k in range(min(N-i,N-j)):
dlist[k].append(log(d(series,i+k,j+k)))
f=open('lyapunov.txt','w')
for i in range(len(dlist)):
if len(dlist[i]):
print>>f, i, sum(dlist[i])/len(dlist[i])
f.close()
Это находит пары, вычисляет ln(расстояния), усредняет; plot lyapunov.txt для наклона = LE.
Из insightbig.com (для финансов, с window):
import numpy as np
from scipy.stats import linregress
import pandas as pd
# Функция LE
def lyapunov_exponent(prices, window=50):
log_returns = np.log(prices / prices.shift(1))
lyap_exp = []
for i in range(len(prices) - window):
segment = log_returns[i:i+window].dropna()
if len(segment) > 2:
slope, _, _, _, _ = linregress(range(len(segment)), segment)
lyap_exp.append(slope)
else:
lyap_exp.append(np.nan)
return np.array([np.nan]*window + lyap_exp)
# Пример: df — DataFrame с 'close'
df = pd.DataFrame({'close': np.random.rand(100)}) # Замените
df["Lyapunov"] = lyapunov_exponent(df["close"])
print(df["Lyapunov"])
Используется в стратегии: if LE < 0 and FDI < 1.5 — buy.
Из Stack Overflow (вариант с cumsum):
import numpy as np
def lyapunov_exponent(series: np.array, threshold: float) -> np.array:
N = len(series)
eps = threshold
L = [np.array([0]*N)]
for i in range(1, N):
diff = np.abs(series[i:]-series[:-i])
dist = np.log(diff)
L.append(np.concatenate([[0]*i, dist]))
L = np.array(L)
tf_L = np.where(L<eps, 1, 0)
count_L = np.zeros_like(tf_L)
for i in range(N):
indices = ( np.array(range(0,N-i)), np.array(range(i,N)) )
count_L[indices] = np.cumsum(tf_L[indices])
avg = np.sum(count_L * L, axis=0) / np.sum(count_L, axis=0)
return avg
# Пример
series = np.random.rand(100)
le = lyapunov_exponent(series, 0.01)
print(le)
Это вычисляет среднее LE по матрице расстояний.
Из nolds (GitHub CSchoel/nolds, measures.py): Хотя полный код не извлечен, типичная функция lyap_r:
import nolds # pip install nolds
import numpy as np
data = np.random.rand(1000)
le = nolds.lyap_r(data, emb_dim=3, lag=1, min_neighs=20)
print(le) # Rosenstein метод
Или lyap_e для Eckmann-Ruelle.
Эти методы интегрируются с phase space для полного анализа: сначала embed, затем LE на embedded данных.