!pip install yfinance --upgrade --no-cache-dir
1 Лабораторна робота № 1
Тема. Аналіз флуктуацій часового ряду
Мета. Навчитися використовувати аналіз флуктуацій та його похідні для отримання нелінійних характеристик часового ряду
1.1 Теоретичні відомості
1.1.1 Аналіз динаміки прибутків, модулів прибутків та волатильностей
Останнім часом вчені все більше цікавляться економічними часовими рядами, і відбувається це за кількох причин, зокрема: (1) економічні часові ряди, такі як індекси акцій, курсів валют, залежать від розвитку великої кількості взаємодіючих систем, і є прикладами складних систем, що широко вивчаються у науці; (2) з’явилась велика кількість доступних баз з даними про економічні системи, що містять інформацію з різними часовими шкалами (починаючи з 1 хвилини і закінчуючи 1 роком). Внаслідок цього вже на даний час існує також велика кількість розроблених методів (зокрема, у статистичній фізиці), спрямованих на отримання характеристик цін акцій чи курсів валют, що еволюціонують у часі.
Дослідження, проведені над часовими рядами, показують, що стохастичний процес, який лежить у основі зміни ціни, характеризується кількома ознаками. Розподіл зміни ціни має виділений хвіст порівняно із Гаусовим розподілом. Функція автокореляції зміни ціни спадає експоненційно з певним характерним часом. Однак, виявляється, що амплітуда зміни ціни, виміряна за абсолютними значеннями чи квадратами цін, показує степеневі кореляції з довго часовою персистентністю аж до кількох місяців, або навіть років. Такі довгочасові залежності краще моделюються з використанням “додаткового процесу”, що в економічній літературі часто називається волатильністю. Волатильність змін ціни акції є мірою того, як сильно ринок схильний до флуктуацій, тобто відхилень ціни від попередніх значень.
Першим кроком при проведенні аналізу є побудова оцінювача волатильності. Ми будемо отримувати волатильність як локальне середнє модуля зміни ціни.
Розуміння статистичних властивостей волатильності має також важливе практичне застосування. Волатильність є інтересом торговців, оскільки визначає ризик і є ключовим входом практично до всіх моделей цін опціонів (вторинного цінного паперу), включаючи і класичну модель Блека-Шоулза. Без задовільних методів оцінювання волатильності трейдерам було б надзвичайно важко визначати ситуації, в яких опціони попадають в недооцінку чи переоцінку.
1.1.2 Визначення волатильності
Термін волатильність представляє узагальнену міру величини ринкових флуктуацій (відхилень). У літературі існує досить багато визначень волатильності, проте ми будемо використовувати наступне: волатильність є локальним середнім модуля зміни ціни на відповідному часовому інтервалі \(T\), що є рухомим параметром нашої оцінки. Для індексу \(X(t)\) визначимо зміну ціни \(G(t)\) як зміну логарифмів індексів,
\[ G(t) = \ln{X(t+\Delta t) - \ln{X(t)}} \cong \left[ X(t+\Delta t) - X(t) \right] \big/ X(t), \tag{1.1}\]
де \(\Delta t\) є часовим інтервалом затримки. Величину (1.1) називають прибутковістю (return). Якщо використовувати границі, то малі зміни \(X(t)\) приблизно відповідають змінам, визначеним другою рівністю. Ми лише підраховуємо час роботи ринку, викидаємо ночі, вихідні та свята із набору даних, тобто, вважаємо, що ринок працює без перерв.
Модуль \(G(t)\) описує амплітуду флуктуацій. У порівнянні зі значеннями \(G(t)\) їх модуль не показує глобальних трендів, але великі значення \(G(t)\) відповідають крахам та великим миттєвим змінам на ринках.
Визначимо волатильність як середнє від \(G(t)\) для часових вікон \(T = n \cdot \Delta t\), тобто
\[ V_{T} = \frac{1}{n}\sum_{t^{'}=t}^{t+n-1}\left| G(t^{'}) \right|, \tag{1.2}\]
де \(n\) є цілим числом. Таке визначення може бути ще узагальнене заміною \(G(t)\) на \(\left| G(t) \right|^{\gamma}\), де \(\gamma > 1\) дає більш виражені великі значення \(G(t)\), в той час як \(0 < \gamma < 1\) виділяє малі значення \(G(t)\).
У цьому визначенні волатильності використовується два параметри: \(\Delta t\) та \(n\). Параметр \(n\) є шаблонним (чи модельним) часовим інтервалом для даних, а параметр \(\Delta t\) є кроком переміщення часового вікна. Зауважимо, що вказане визначення волатильності має внутрішню помилку, а саме: вибір більшого часового інтервалу \(T\) веде до збільшення точності визначення волатильності. Однак, велике значення \(T\) також включає погане розбиття часу на інтервали, що веде, у свою чергу, до врахування не всієї прихованої у ряді інформації.
1.1.3 Визначення кореляцій
Для визначення кореляцій часового ряду використовується функція автокореляції. Саме поняття автокореляції означає кореляцію часового ряду самого з собою (між попередніми та наступними значеннями). Автокореляцію іноді називають послідовною кореляцією, що означає кореляцію між членами ряду чисел, розташованих у певному порядку. Також синонімами цього терміну є лагова кореляція та персистентність. Наприклад, часто зустрічається автокореляція геофізичних процесів, що означає перенесення залишкового процесу на наступні часові проміжки.
Позитивно автокорельований часовий ряд часто називають персистентним, що значить існування тенденції слідування великих значень за великими та малих за малими, інакше позитивно корельований часовий ряд можна назвати інертним.
Візьмемо \(N\) пар спостережень двох змінних \(x\) та \(y\). Коефіцієнт кореляції між парами \(x\) та \(y\) визначається як
\[ r = \left[ \sum \left( x_i - \bar{x} \right) \left( y_i - \bar{y} \right) \right] \Bigg/ \left[ \sqrt{\sum \left( x_i - \bar{x} \right)^{2}} \sqrt{\sum \left( y_i - \bar{y} \right)^{2}} \right], \tag{1.3}\]
де сума знаходиться за всіма \(N\) спостереженням.
Таким же чином можна визначати й автокореляцію, або ж кореляцію всередині досліджуваного часового ряду. Для автокореляції першого порядку береться лаг (часова затримка), рівний 1 часовій одиниці. Отже, автокореляція першого порядку використовує перші \(N−1\) спостережень \(x_t, t = 1,..., N−1\) та наступні \(N−1\) спостережень \(x_t , t = 2,..., N\).
Кореляція між \(x_t\) та \(x_t + 1\) визначається як
\[ r_1 = \left[ \sum_{t=1}^{N-1} \left( x_t - \bar{x} \right) \left( x_{t+1} - \bar{x} \right) \right] \Bigg/ \left[ \sum_{t=1}^{N}\left( x_t - \bar{x} \right)^2 \right], \tag{1.4}\]
де \(x\) — це середнє для досліджуваного періоду.
Рівняння (1.4) може бути узагальнене для отримання кореляції між спостереженнями, розділеними \(k\) часовими інтервалами:
\[ r_k = \left[ \sum_{t=1}^{N-k} \left( x_t - \bar{x} \right) \left( x_{t+k} - \bar{x} \right) \right] \Bigg/ \left[ \sum_{t=1}^{N}\left( x_t - \bar{x} \right)^2 \right]. \tag{1.5}\]
Значення \(r_k\) називається коефіцієнтом автокореляції з лагом \(k\). Графік функції автокореляції як залежності \(r_k\) від \(k\) також називають корелограмою.
1.2 Хід роботи
Для подальшої роботи з моделювання складних систем візьмемо з основу бібліотеку yfinance
, що дозволяє працювати з даними фінансових ринків засобами мови програмування Python.
Yahoo!, Y!Finance, and Yahoo! finance є зареєстрованими товарними знаками Yahoo, Inc.
yfinance
не є афілійованим, схваленим або перевіреним Yahoo, Inc. Це інструмент з відкритим вихідним кодом, який використовує загальнодоступні API Yahoo, і призначений для дослідницьких та освітніх цілей.
Ви повинні звернутися до умов використання Yahoo! (сюди, сюди і сюди) для отримання детальної інформації про ваші права на використання фактично завантажених даних. Пам’ятайте — фінансовий API Yahoo! призначений лише для особистого використання
Для встановлення бібліотеки yfinance
можете скористатися наступною командою:
Гітхаб репозиторій містить більше інформації по самій бібліотеці та помилкам, що можуть виникнути та їх потенційним рішенням.
1.2.1 Вступ до модуля Ticker()
Перш за все імпортуємо бібліотеку yfinance
за допомогою наступної команди:
import yfinance as yf
Модуль Ticker()
дозволяє отримувати ринкові та метадані для цінного паперу, використовуючи Python:
= yf.Ticker("MSFT")
msft print(msft)
yfinance.Ticker object <MSFT>
Можна вилучити всю інформацію по досліджуваному індексу:
# отримати інформацію по індексу
msft.info
Можна вилучити ринкові значення за максимальний період часу:
# отримати ринкові історичні значення індексу
="max") msft.history(period
Open | High | Low | Close | Volume | Dividends | Stock Splits | |
---|---|---|---|---|---|---|---|
Date | |||||||
1986-03-13 00:00:00-05:00 | 0.054485 | 0.062498 | 0.054485 | 0.059827 | 1031788800 | 0.0 | 0.0 |
1986-03-14 00:00:00-05:00 | 0.059827 | 0.063032 | 0.059827 | 0.061963 | 308160000 | 0.0 | 0.0 |
1986-03-17 00:00:00-05:00 | 0.061963 | 0.063566 | 0.061963 | 0.063032 | 133171200 | 0.0 | 0.0 |
1986-03-18 00:00:00-05:00 | 0.063032 | 0.063566 | 0.060895 | 0.061429 | 67766400 | 0.0 | 0.0 |
1986-03-19 00:00:00-05:00 | 0.061429 | 0.061963 | 0.059827 | 0.060361 | 47894400 | 0.0 | 0.0 |
... | ... | ... | ... | ... | ... | ... | ... |
2025-02-10 00:00:00-05:00 | 413.709991 | 415.459991 | 410.920013 | 412.220001 | 20817900 | 0.0 | 0.0 |
2025-02-11 00:00:00-05:00 | 409.640015 | 412.489990 | 409.299988 | 411.440002 | 18140600 | 0.0 | 0.0 |
2025-02-12 00:00:00-05:00 | 407.209991 | 410.750000 | 404.369995 | 409.040009 | 19121700 | 0.0 | 0.0 |
2025-02-13 00:00:00-05:00 | 407.000000 | 411.000000 | 406.359985 | 410.540009 | 23891700 | 0.0 | 0.0 |
2025-02-14 00:00:00-05:00 | 407.790009 | 408.910004 | 405.880005 | 408.429993 | 22680000 | 0.0 | 0.0 |
9809 rows × 7 columns
Окрім цього, yfinance
дозволяє отримати інформацію по дивідентам та сплітам фінансового індексу:
# показувати дії (дивіденди, спліти)
msft.actions
Dividends | Stock Splits | |
---|---|---|
Date | ||
1987-09-21 00:00:00-04:00 | 0.00 | 2.0 |
1990-04-16 00:00:00-04:00 | 0.00 | 2.0 |
1991-06-27 00:00:00-04:00 | 0.00 | 1.5 |
1992-06-15 00:00:00-04:00 | 0.00 | 1.5 |
1994-05-23 00:00:00-04:00 | 0.00 | 2.0 |
... | ... | ... |
2023-11-15 00:00:00-05:00 | 0.75 | 0.0 |
2024-02-14 00:00:00-05:00 | 0.75 | 0.0 |
2024-05-15 00:00:00-04:00 | 0.75 | 0.0 |
2024-08-15 00:00:00-04:00 | 0.75 | 0.0 |
2024-11-21 00:00:00-05:00 | 0.83 | 0.0 |
93 rows × 2 columns
# продемонструвати дивіденти
msft.dividends
Date
2003-02-19 00:00:00-05:00 0.08
2003-10-15 00:00:00-04:00 0.16
2004-08-23 00:00:00-04:00 0.08
2004-11-15 00:00:00-05:00 3.08
2005-02-15 00:00:00-05:00 0.08
...
2023-11-15 00:00:00-05:00 0.75
2024-02-14 00:00:00-05:00 0.75
2024-05-15 00:00:00-04:00 0.75
2024-08-15 00:00:00-04:00 0.75
2024-11-21 00:00:00-05:00 0.83
Name: Dividends, Length: 84, dtype: float64
# продемонструвати спліти
msft.splits
Date
1987-09-21 00:00:00-04:00 2.0
1990-04-16 00:00:00-04:00 2.0
1991-06-27 00:00:00-04:00 1.5
1992-06-15 00:00:00-04:00 1.5
1994-05-23 00:00:00-04:00 2.0
1996-12-09 00:00:00-05:00 2.0
1998-02-23 00:00:00-05:00 2.0
1999-03-29 00:00:00-05:00 2.0
2003-02-18 00:00:00-05:00 2.0
Name: Stock Splits, dtype: float64
Для методу history()
доступні аргументи:
- period: період даних для завантаження (або використовуйте параметр period, або використовуйте start і end). Допустимі періоди: 1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max;
- interval: інтервал даних (внутрішньоденні дані не можуть перевищувати 60 днів) Допустимі інтервали 1m, 2m, 5m, 15m, 30m, 60m, 90m, 1h, 1d, 5d, 1wk, 1mo, 3mo;
- start: Якщо не використовується період — завантажте рядок дати початку у форматі (YYYY-MM-DD) або datetime;
- end: Якщо не використовується період — завантажте рядок дати закінчення (YYYY-MM-DD) або datetime;
- prepost: Включати в результати попередні та пост ринкові дані (За замовчуванням False);
- auto_adjust: Автоматично налаштовувати всі OHLC (ціни відкриття, закриття, найбільшу та найменшу) (За замовчуванням True);
- actions: Завантажувати події дивідендів та дроблення акцій (За замовчуванням True).
1.2.2 Одночасне вивантаження декількох ринкових активів
Як і до цього, ви також можете завантажувати дані для кількох тикерів одночасно:
= yf.download("SPY AAPL",
data ="2017-01-01",
start="2017-04-30") # вивантажуємо дані,
end# зберігаємо до змінної data
# виводимо перші 5 рядків нашого масиву даних data.head()
YF.download() has changed argument auto_adjust default to True
[*********************100%***********************] 2 of 2 completed
Price | Close | High | Low | Open | Volume | |||||
---|---|---|---|---|---|---|---|---|---|---|
Ticker | AAPL | SPY | AAPL | SPY | AAPL | SPY | AAPL | SPY | AAPL | SPY |
Date | ||||||||||
2017-01-03 | 26.862425 | 197.288879 | 26.904054 | 197.805660 | 26.540955 | 196.097648 | 26.781479 | 197.113688 | 115127600 | 91366500 |
2017-01-04 | 26.832361 | 198.462570 | 26.945687 | 198.611472 | 26.769918 | 197.612941 | 26.793045 | 197.621695 | 84472400 | 78744400 |
2017-01-05 | 26.968813 | 198.304901 | 27.026631 | 198.462571 | 26.783793 | 197.499070 | 26.809234 | 198.191043 | 88774400 | 78379000 |
2017-01-06 | 27.269466 | 199.014420 | 27.327285 | 199.487402 | 26.936432 | 197.866973 | 27.008126 | 198.418797 | 127007600 | 71559900 |
2017-01-09 | 27.519245 | 198.357483 | 27.621006 | 198.891786 | 27.276409 | 198.322439 | 27.278720 | 198.751638 | 134247600 | 46939700 |
Для отримання конкретно цін закриття індексу SPY вам варто використовувати наступну команду: data['Close']['SPY']
Але, якщо вам необхідно згрупувати дані за їх символом, можна скористатися наступним записом:
= yf.download("SPY AAPL",
data ="2017-01-01",
start="2017-04-30",
end="ticker") group_by
[*********************100%***********************] 2 of 2 completed
Тепер для звернення до цін закриття індексу SPY вам треба використовувати наступний запис: data['SPY']['Close']
.
Метод download()
приймає додатковий параметр threads
для швидшої обробки великої кількості фінансових індексів одночасно.
Для подальшої роботи нас ще будуть цікавати наступні бібліотеки:
matplotlib
: комплексна бібліотека для створення статичних, анімованих та інтерактивних візуалізацій на Python. Matplotlib робить прості речі простими, а складні — можливими;pandas
: програмна бібліотека написана для мови програмування Python для маніпулювання та аналізу даних. Зокрема, вона пропонує структури даних та операції з числовими таблицями та часовими рядами;numpy
: бібліотека, що додає підтримку великих багатовимірних масивів і матриць, а також колекцію високорівневих математичних функцій для роботи з цими масивами;neurokit2
: зручна бібліотека, що забезпечує легкий доступ до розширених процедур обробки біосигналів. Дослідники та клініцисти без глибоких знань програмування або біомедичної обробки сигналів можуть аналізувати фізіологічні дані за допомогою лише двох рядків коду. Перевага даної бібліотеки полягає в тому, що вона надає функціонал, який можна використовувати не лише для біомедичних сигналів, але й для фінансових, фізичних тощо.
Встановити кожну з даних бібліотек можна в наступний спосіб: !pip install *назва бібліотеки*
:
!pip install matplotlib pandas numpy neurokit2
Далі нам треба буде визначити стиль рисунків для виведення та збереження. Встановимо наступну бібліотеку:
# для встановлення останньої версії (із PyPI)
!pip install SciencePlots
Імпортуємо кожну із зазначених бібліотек:
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import neurokit2 as nk
import scienceplots
# магічна команда для вбудування рисунків у середовище jupyter блокнотів
%matplotlib inline
Виконаємо налаштування стилю наших подальших рисунків:
'science', 'notebook', 'grid']) # стиль, що використовуватиметься
plt.style.use([# для виведення рисунків
= 16
size = {
params 'figure.figsize': (8, 6), # встановлюємо ширину та висоту рисунків за замовчуванням
'font.size': size, # розмір фонтів рисунку
'lines.linewidth': 2, # товщина ліній
'axes.titlesize': 'small', # розмір титулки над рисунком
'axes.labelsize': size, # розмір підписів по осям
'legend.fontsize': size, # розмір легенди
'xtick.labelsize': size, # розмір розмітки по осі Ох
'ytick.labelsize': size, # розмір розмітки по осі Ох
"font.family": "Serif", # сімейство стилів підписів
"font.serif": ["Times New Roman"], # стиль підпису
'savefig.dpi': 300, # якість збережених зображень
'axes.grid': False # побудова сітки на самому рисунку
}
# оновлення стилю згідно налаштувань plt.rcParams.update(params)
Представлені налаштування є орієнтовними і можуть змінюватись у ході наступних лабораторних роботах. Ви можете встановлювати власні налаштування. На сайті бібліотеки matplotlib можна ознайомитись з усіма можливими командами.
Розглянемо можливість використання всіх згаданих показників у якості індикаторів або індикаторів-передвісників кризових явищ. Для прикладу завантажимо часовий ряд Біткоїна за період з 1 вересня 2015 по 1 березня 2020, використовуючи yfinance
:
= 'BTC-USD' # Символ індексу
symbol = "2015-09-01" # Дата початку зчитування даних
start = "2020-03-01" # Дата закінчення зчитування даних
end
= yf.download(symbol, start, end) # вивантажуємо дані
data = data['Close'].copy() # зберігаємо саме ціни закриття
time_ser
= 'time, days' # підпис по вісі Ох
xlabel = symbol # підпис по вісі Оу ylabel
[*********************100%***********************] 1 of 1 completed
Представлені в подальшому методи є універсальними. Іншими словами, ви можете їх використовувати не лише для фінансових часових рядів, але й для біологічних, фізичних та інших систем, що існують в осяжній нами реальності та можуть бути репрезентовані у вигляду часового ряду. Може бути так, що наявні у вас дані, наприклад, представлені у форматі текстового документа (.txt). Нижче представлено приклад зчитування текстового файлу, що представляє залежність між напруженням і деформацією твердого тіла. Представлену далі залежність було отримано в результаті механічних випробувань певного металу. Зрозуміло, що аналіз результатів і висновки у цьому випадку залежать від того, з яким рядом ми працюємо
= 'sMpa11' # Символ індексу
symbol
= "databases\sMpa11.txt" # шлях по якому здійснюється зчитування файлу
path = pd.read_csv(path, # зчитування даних
data =[symbol])
names= data[symbol].copy() # копіюємо значення кривої
time_ser # "напруга-видовження" до окремої змінної
= r'$\varepsilon$' # підпис по вісі Ох
xlabel = symbol # підпис по вісі Оу ylabel
Тепер виведемо значення, що були зчитані з текстового документа:
= plt.subplots(1, 1) # Створюємо порожній графік
fig, ax # Додаємо дані до графіку
ax.plot(time_ser.index, time_ser.values) # Додаємо легенду
ax.legend([symbol])
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
=45) # оберт позначок по осі Ох на 45 градусів
plt.xticks(rotation
f'{symbol}.jpg') # Зберігаємо графік
plt.savefig(; # Виводимо графік plt.show()

Для даного ряду, за потребою, можна виконувати подальші розрахунки.
Знову повертаємось до Біткоїна. Для відтворення подальших розрахунків з Біткоїном блок коду в якому зчитувалась та виводилась крива “напруга-видовження” треба проігнорувати
Виведемо значення Біткоїна:
= plt.subplots(1, 1) # Створюємо порожній графік
fig, ax # Додаємо дані до графіку
ax.plot(time_ser.index, time_ser.values) # Додаємо легенду
ax.legend([symbol])
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
=45) # оберт позначок по осі Ох на 45 градусів
plt.xticks(rotation
f'{symbol}.jpg') # Зберігаємо графік
plt.savefig(; # Виводимо графік plt.show()

Видно, що ряд нестаціонарний, що викликає певні ускладнення для подальшого аналізу. Тому перейдемо до прибутковостей, які вже є стаціонарними, а нормалізація стандартним відхиленням дозволяє легко порівнювати їх розподіл з розподілом Гауса.
Прибутковості розраховуватимуться згідно рівняння (1.1). У Python для цього ми використовуватимемо метод pct_change()
, що доступний нам завдяки бібліотеці pandas
.
Стандартизовані прибутковості можна визначити як \(g(t) = \left[G(t) - \mu \right] / \sigma\), де \(\mu\) відповідає середньому значенню прибутковостей за досліджуваний часовий інтервал, а \(\sigma\) представляє стандартне відхилення.
= time_ser.copy() # копіюємо значення вихідного ряду для збереження
ret # його від змін
= ret.pct_change() # знаходимо прибутковості
ret -= ret.mean() # вилучаємо середнє
ret /= ret.std() # ділимо на стандартнє відхилення
ret
= ret.dropna().values # видаляємо всі можливі нульові значення ret
Виводимо отриманий результат:
= plt.subplots() # Створюємо порожній графік
fig, ax 1:], ret) # Додаємо дані до графіку
ax.plot(time_ser.index[# Додаємо легенду
ax.legend([symbol]) # Додаємо підпис для вісі Ох
ax.set_xlabel(xlabel) + ' прибутковості') # Додаємо підпис для вісі Оу
ax.set_ylabel(ylabel = 3.0,
ax.axhline(y = 'r',
color = '--') # Додаємо горизонтальну лінію,
linestyle # що роз межує 3 сигма події
= -3.0,
ax.axhline(y = 'r',
color = '--') # Додаємо горизонтальну лінію,
linestyle # що розмежує -3 сигма події
=45) # оберт позначок по осі Ох на 45 градусів
plt.xticks(rotation
f'Прибутковості{symbol}.jpg') # Зберігаємо графік
plt.savefig(; # Виводимо графік plt.show()

Зверніть увагу, що флуктуації нормалізованих прибутковостей досить часто перевищують величину \(\pm 3\sigma\), що, як відомо, надзвичайно рідко спостерігається для незалежних подій. Цей факт можна відобразити шляхом порівняння функції розподілу нормалізованих флуктуацій з розподілом Гауса (Рис. 1.4). Очевидно, що хвости розподілу вихідного ряду містять значні флуктуації, вони досить помітні (часто кажуть “важкі” у порівнянні з самою “головою” розподілу).
Для побудови нормального розподілу скористаємось бібліотекою scipy
. Встановити її можна за аналогією з попередніми бібліотеками.
# Для встановлення останньої версії scipy
!pip install scipy
from scipy.stats import norm # імпорт модуля norm для побудови Гаусового розподілу
Функція щільності ймовірності norm
для дійсних значень \(x\) має наступний вигляд: \(f(x) = \exp{(-x^2/2)} \big/ \sqrt{2\pi}\).
= norm.fit(ret)
mu, sigma
= np.linspace(ret.min(), ret.max(), 10000) # Генеруємо 10000 значень для побудови
x # Гаусового розподілу
= norm.pdf(x, mu, sigma) # Отримання значень функції щільності p
= plt.subplots(1, 1) # Створюємо порожній графік
fig, ax ='Гаусіан') # Додаємо дані до графіку
ax.plot(x, p, label=50, # Побудова гістограми для прибутковостей
ax.hist(ret, bins=True,
density=0.6,
alpha='g',
color='Прибутковості '+ symbol)
label
# Додаємо легенду
ax.legend() "g") # Додаємо підпис для вісі Ох
ax.set_xlabel(r"$f(g)$") # Додаємо підпис для вісі Оу
ax.set_ylabel('log')
ax.set_yscale(
f'Гаус + прибутковості {symbol}.jpg') # Зберігаємо графік
plt.savefig(; # Виводимо графік plt.show()

Як ми можемо бачити, крива Гауса відхиляється від істинної частоти настання подій, що перевищують \(\pm 3\sigma\), і ми можемо стверджувати, що прибутковості не є незалежними. Підтвердження цьому факту будемо шукати шляхом вивчення кореляційних властивостей нашого часового ряду.
Для простоти обчислень скористаємось функцією signal_autocor()
бібліотеки neurokit2
. Виглядає дана функція наступним чином:
signal_autocor(signal, lag=None, demean=True, method='auto', show=False)
Параметри:
- signal (Union[list, np.array, pd.Series]) — вектор значень;
- lag (int) — часовий лаг. Якщо вказано, буде повернуто одне значення автокореляції сигналу з його власним лагом;
- demean (bool) — якщо має значення
True
, від сигналу буде відніматися середнє значення сигналу перед обчисленням автокореляції; - method (str) — використання
"auto"
запускаєscipy.signal.correlate
швидшого алгоритму. Інші методи зберігаються з причин застарілості, але не рекомендуються. Вони включають"correlation"
(за допомогоюnp.correlate()
) або"fft"
(швидке перетворення Фур’є); - show (bool) — якщо значення
True
, побудувати графік автокореляції для всіх значень затримки.
Повертає:
- r (float) — крос-кореляція сигналу із самим собою на різних часових лагах. Мінімальний часовий лаг дорівнює 0, максимальний часовий лаг дорівнює довжині сигналу. Або значення кореляції на певному часовому лазі, якщо лаг не дорівнює
None
; - info (dict) — cловник, що містить додаткову інформацію, наприклад, довірчий інтервал.
len(time_ser.values)
1643
# розрахунок автокореляції
= nk.signal_autocor(time_ser.values.flatten(),
r_init, _ ='correlation') # для вихідних значень ряду
method= nk.signal_autocor(ret.flatten(),
r_ret, _ ='correlation') # для прибутковостей
method= nk.signal_autocor(np.abs(ret.flatten()),
r_vol, _ ='correlation') # для модулів прибутковостей
method
= np.arange(1, len(r_ret) + 1) # генерація лагів r_range
= plt.subplots(1, 1) # Створюємо порожній графік
fig, ax
1:], label=symbol) # Додаємо дані до графіку
ax.plot(r_range, r_init[=r'$g(t)$')
ax.plot(r_range, r_ret, label=r'$V_{T}$')
ax.plot(r_range, r_vol, label
# Додаємо легенду
ax.legend() r"Часовий лаг $k$") # Додаємо підпис для вісі Ох
ax.set_xlabel(r"Автокореляція $r$") # Додаємо підпис для вісі Оу
ax.set_ylabel(-1.1, 1.1) # Встановлюємо обмеження по вісі Oy
ax.set_ylim(
f'Автокореляції {symbol}.jpg') # Зберігаємо графік
plt.savefig(; # Виводимо графік plt.show()

Але, досліджуючи складні системи, варто пам’ятати, що їх складність є варіативною. Тому і внутрішні кореляції системи на різних часових лагах також варіюються з плином часу. Із цього випливає, що подальщі розрахунки варто виконувати не для всього ряду, а для його фрагментів.
Сформулюємо алгоритм ковзного (рухомого) вікна. Виділимо фрагмент часового ряду (вікно), розрахуємо необхідні міри складності, змістимо вікно вздовж часового ряду на заздалегідь визначену величину (крок) і повторимо процедуру до вичерпання часового ряду. Далі, порівнюючи динаміку фактичного часового ряду і відповідних мір складності, ми матимемо змогу судити про характерні зміни в динаміці міри складності зі зміною досліджуваної системи. Якщо та чи інша міра складності поводиться характерним чином для всіх особливих періодів (наприклад, крахів), зменшується або збільшується у передкризовий період, то вона може служити їх індикатором або передвісником.
Розглянемо як поводитиме себе функція автокореляцій та волатильність в рамках алгоритму ковзного вікна.
Спочатку визначимо параметри:
= 4 # вид ряду:
ret_type # 1 - вихідний,
# 2 - детрендований (різниця між теп. значенням та попереднім)
# 3 - прибутковості звичайні,
# 4 - стандартизовані прибутковості,
# 5 - абсолютні значення (волатильності),
# 6 - стандартизований вихідний ряд
= len(time_ser) # довжина всього ряду
length
= 250 # довжина вікна - період у межах якого розраховуватимуться наші індикатори
window = 1 # крок зміщення вікна
tstep = [] # масив значень волатильностей
volatility = [] # масив значень автокореляції при змінній lag autocorr
Далі розпочнемо розрахунки. Для відслідковування прогресу зміщення ковзного вікна скористаємось бібліотекою tqdm
. Її можна встановити аналогічно попереднім бібліотекам:
!pip install tqdm
Імпортуємо модуль для візуалізації прогресу:
from tqdm import tqdm
І тепер приступимо до виконання віконної процедури:
for i in tqdm(range(0,length-window,tstep)): # Фрагменти довжиною window
# з кроком tstep
= time_ser.iloc[i:i+window].copy() # відбираємо фрагмент
fragm
# подальшому відбираємо потрібний тип ряду
if ret_type == 1: # вихідні значення
pass
elif ret_type == 2: # різниці
= fragm[1:] - fragm[:-1]
fragm elif ret_type == 3: # прибутковості
= fragm.pct_change()
fragm elif ret_type == 4: # стандартизовані прибутковості
= fragm.pct_change()
fragm -= fragm.mean()
fragm /= fragm.std()
fragm elif ret_type == 5: # абсолютні значення прибутковостей
= fragm.pct_change()
fragm -= fragm.mean()
fragm /= fragm.std()
fragm = fragm.abs()
fragm elif ret_type == 6: # стандартизований вихідний ряд
-= fragm.mean()
fragm /= fragm.std()
fragm
= fragm.dropna().values # видаляємо зайві нульові значення, якщо є
fragm
# розрахунок віконної автокореляції
= nk.signal_autocor(fragm.flatten(), method='correlation')
r_window, _
# розрахунок волатильності по модулям прибутковостей
= np.mean(np.abs(fragm))
vol_window
# збереження результатів до масивів
volatility.append(vol_window)1]) autocorr.append(r_window[
100%|██████████| 1393/1393 [00:02<00:00, 566.46it/s]
Збережемо результати в окремих текстових файлах:
# збереження результатів ковзної автокореляції
f"autocorr_name={symbol}_ \
np.savetxt( window={window}_step={tstep}_ \
rettype={ret_type}.txt", autocorr)
# збереження результатів ковзної волатильності
f"volatility_name={symbol}_ \
np.savetxt( window={window}_step={tstep}_ \
rettype={ret_type}.txt", volatility)
Нарешті, порівняємо динаміку вихідного ряду і розрахованих похідних. Для цього врахуємо, що автокореляцію і волатильність ми знаходили для рухомого вікна. Результати представлено на Рис. 1.6.
= plt.subplots(1, 1)
fig, ax
= ax.twinx()
ax2 = ax.twinx()
ax3
"axes", 1.03))
ax2.spines.right.set_position(("axes", 1.10))
ax3.spines.right.set_position((
= ax.plot(time_ser.index[window:length:tstep],
p1,
time_ser.values[window:length:tstep],"b-", label=fr"{ylabel}")
= ax2.plot(time_ser.index[window:length:tstep],
p2, "g-", label=r"$A$")
autocorr,
= ax3.plot(time_ser.index[window:length:tstep],
p3, "m-", label=r"$V$")
volatility,
ax.set_xlabel(xlabel)f"{ylabel}")
ax.set_ylabel(
ax.yaxis.label.set_color(p1.get_color())
ax2.yaxis.label.set_color(p2.get_color())
ax3.yaxis.label.set_color(p3.get_color())
= dict(size=4, width=1.5)
tkw ='y', colors=p1.get_color(), **tkw)
ax.tick_params(axis=90, axis='y', colors=p2.get_color(), **tkw)
ax2.tick_params(rotation=30, axis='y', colors=p3.get_color(), **tkw)
ax3.tick_params(rotation
=45, axis='x', **tkw)
ax.tick_params(rotation
=[p1, p2, p3], fontsize=18)
ax3.legend(handles
f"all_name={symbol}_ret={ret_type}_\
plt.savefig( wind={window}_step={tstep}.jpg")
; plt.show()

Аналізуючи графік, можна зробити висновок, що у певні моменти спостерігалися стрибки волатильності (як і автокореляції) із поступовим зменшенням її до попереднього рівня, що може бути наслідком збурень у процесі роботи ринку. Аналіз таких збурень, їх частоти та сили, дозволяє виявляти приховані закономірності роботи ринку.
1.3 Висновок
Таким чином, аналіз флуктуацій прибутковостей та волатильностей шляхом побудови функції автокореляції та розподілу ймовірності дозволяє отримати певні висновки, що можуть допомогти в роботі із аналізованими часовими рядами і ринком, з якого взято зазначені часові ряди. Зокрема, у даному випадку, можна надавати корисні рекомендації фінансовим аналітикам.
1.4 Завдання для самостійної роботи
- Отримати індекс часового ряду у викладача
- Провести дослідження згідно інструкції
- Дослідити зміни розрахованих величин для вікон 100 і 500, з кроком 1. Порівняти результати
- Зробити висновки
1.5 Контрольні питання
- Порівняйте вид залежностей флуктуацій цін і прибутковостей. Чому при розрахунках користуються не цінами, а прибутковостями?
- Яку характеристику ряду визначає волатильність?
- У чому причина різних залежностей для прибутковостей та їх модулів?
1.6 Додаток
Для обрання часового індексу часового ряду використаємо дані, що розміщенні на сайті Yahoo! Finance. Оскільки окремі фінансові показники не завжди є доступними, будемо використовувати список компаній, що входять до індексу DJIA. За вказаним посиланням та номером у списку групи оберіть компанію, що входить до індексу та проведіть відповідні розрахунки. Порівняйте отримані результати з такими ж для Біткоїна.