8  Лабораторна робота № 8

Тема. Дослідження процесів самоорганізації в складних системах із використання теорії випадкових матриць

Мета. Навчитись використовувати методи теорії випадкових матриць для дослідження колективних процесів у складних системах

8.1 Теоретичні відомості

Вивчення статистичних властивостей матриць з незалежними випадковими елементами — випадкових матриць — має багату історію, що починається з ядерної фізики  [17], де проблема з’явилася більше 50 років тому при дослідженні енергетичних рівнів складних ядер. Теорія випадкової матриці (ТВМ) була розвинена в цьому контексті Вігнером (Wigner), Дайсоном (Dyson), Метою (Mehta) та іншими для пояснення статистики рівнів енергії складних квантових систем. Дослідники постулювали, що функція Гамільтона, яка описує важкі ядра, може бути задана матрицею \(H\) з незалежними випадковими елементами \(H_{ij}\), отриманими з розподілу імовірності. Відштовхуючись від цього припущення було зроблено низку вражаючих передбачень, які було підтверджено експериментально. Для складних квантових систем результати на основі ТВМ представляють середнє за всіма можливими взаємодіями. Відхилення від універсальних передбачень ТВМ відображують системну специфіку, невипадкові властивості системи, забезпечуючи ключові підходи до розуміння базової взаємодії системи. Недавні дослідження, що використовували методи аналізу ТВМ до аналізу властивостей матриці взаємних кореляцій \(C\) для економічних систем, показують, що близько 98% власних значень матриці \(C\) співпадають зі значеннями, отримуваними з використанням ТВМ, таким чином пропонуючи задовільний рівень хаотичності у вимірюваних крос-кореляціях. Також було знайдено, що існують відхилення від передбачень за допомогою ТВМ у близько 2% найбільших власних значень. Ці результати викликають наступні питання:

  1. Яка можлива інтерпретація для відхилень від ТВМ?
  2. Що можна сказати про структуру C з цих результатів?
  3. Яке практичне значення отриманих результатів?

Шляхом комп’ютерного моделювання виявлено, що найбільше власне значення матриці \(C\) представляє вплив ринку в цілому. Аналіз змісту власних значень, що відхиляються від ТВМ, показує існування взаємних кореляцій між акціями того ж самого типу діяльності, найбільш капіталізованими акціями, і акціями фірм, що мають бізнес у певному географічному секторі (локалізовані територіально). Обчислюючи скалярний добуток власних векторів від одного періоду часу до наступного, можна побачити, що власні вектори, що відхиляються, мають різні ступені стабільності в часі, визначеному кількісно величиною скалярного добутку. Найбільші два-три власних вектори стійкі протягом тривалих періодів часу, у той час як для іншої частини власних векторів, що відхиляються, стабільність у часі зменшується як тільки відповідні власні значення наближаються до верхньої межі ТВМ.

8.1.1 Знаходження коефіцієнтів матриці крос-кореляцій

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

Для визначення кількісно кореляцій спочатку обчислюється зміна цін (прибутковості) акції \(i=1,...,N\) за час \(\Delta t\),

\[ G_{i}(t) = \ln S_i(t+\Delta t) - \ln S_i(t), \tag{8.1}\]

де \(S_i(t)\) позначає ціну акції \(i\). Оскільки різні ціни мають різні рівні змінюванності (стандартні відхилення), визначатимемо стандартизовану прибутковість

\[ g_i(t) \equiv \left[ G_i(t) - \left\langle G_i \right\rangle \right] \big/ \sigma_i, \tag{8.2}\]

де \(\sigma_i \equiv \sqrt{\left\langle G_{i}^{2} \right\rangle - \left\langle G_i \right\rangle^{2}}\) — стандартне відхилення \(G_i\), а \(\left\langle...\right\rangle\) позначає середнє значення за досліджуваний період часу. Тоді знаходження матриці кореляцій \(C\) зводиться до обчислення формули:

\[ C_{ij} \equiv \left\langle g_i(t)g_j(t) \right\rangle. \tag{8.3}\]

За побудовою, елементи \(C_{ij}\) обмежені областю \(−1 \leq C_{ij} \leq 1\), де \(C_{ij} = 1\) відповідає повним кореляціям, \(C_{ij} = -1\) — повним антикореляціям, і \(C_{ij} = 0\) свідчить про некорельованність пар акцій.

Труднощі в аналізі важливості та значення коефіцієнтів крос-кореляції \(C_{ij}\) виникають внаслідок кількох причин, що полягають в наступному:

  • ринкові умови з часом змінюються і взаємна кореляція, що існує між будь-якою парою акцій, може бути не постійною (нестаціонарною);
  • скінчена довжина досліджуваного ряду, доступного для оцінювання взаємних кореляцій, додає так званий “шум вимірювання” — чим коротший досліджуваний ряд — тим менш точними будуть отримувані значення.

Яким же чином можна виділяти з \(C_{ij}\) ті акції, що залишилися корельованими на розглядуваному періоді часу? Щоб відповісти на це питання, перевіримо статистику \(C\) у порівнянні із так званою “нульовою гіпотезою” випадкової кореляційної матриці — матриці кореляцій, побудованої із взаємно некорельованих часових рядів. Якщо властивості \(C\) відповідають властивостям для випадкової матриці кореляцій, тоді можна говорити про те, що значення емпірично вимірюваних властивостей \(C\) випадкові. Навпаки, відхилення властивостей \(C\) від таких же властивостей для випадкової кореляційної матриці передає інформацію про “справжні” кореляції. Отже, нашою метою є порівняння властивостей \(C\) з такими ж властивостями випадкової матриці кореляцій і розділ властивостей \(C\) на дві групи: (a) частина \(C\), що відповідає властивостям випадкової кореляційної матриці (“шум”) і (b) частина \(C\), що відхиляється (“інформація”).

8.1.2 Розподіл власних значень

Для отримання інформації про взаємні кореляції \(C\) необхідно порівняти властивості \(C\) з такими ж властивостями випадкової матриці крос-кореляцій  [8]. У матричній нотації така матриця може бути виражена як

\[ C = \frac{1}{L} GG^{T}, \tag{8.4}\]

де \(G\) — матриця розміру \(N \times L\) з елементами \(g_{im}=g_i(m\Delta t), i=1,...,N; m=0,...,L-1\) і \(G^{T}\) позначає транспонування \(G\). Розглянемо випадкову кореляційну матрицю

\[ R = \frac{1}{L} AA^{T}, \tag{8.5}\]

де \(A\) — матриця розміру \(N \times L\), що містить \(N\) часових рядів із \(L\) випадковими елементів \(a_{im}\) з нульовим середнім і одиничним відхиленням, що означають взаємну некорельованість. За побудовою \(R\) належить до типу матриць, які часто називають матрицями Вішарта у багатовимірній статистиці  [9].

Статистичні властивості випадкових матриць \(R\) відомі  [10,11]. Зокрема, у наближенні \(N \to \infty\), \(L \to \infty\), такому, що \(Q \equiv L/N(>1)\) фіксоване, показано аналітично  [11], що функція розподілу щільності імовірності \(P_{rm}(\lambda)\) власних значень \(\lambda\) випадкової матриці кореляції \(R\) визначається як

\[ P_{rm}(\lambda) = \frac{Q}{2\pi}\frac{\sqrt{(\lambda_{+} - \lambda)(\lambda - \lambda_{-})}}{\lambda}, \tag{8.6}\]

із \(\lambda\) в межах границь \(\lambda_{-} \leq \lambda_{i} \leq \lambda_{+}\), де \(\lambda_{-}\) і \(\lambda_{+}\) — найменше та найбільше власні значення \(R\), які можна визначити аналітично як

\[ \lambda_{\pm} = 1 + 1/Q \pm 2\sqrt{1/Q}. \tag{8.7}\]

Вираз (8.6) є точним для випадку розподілених за Гаусом матричних елементів \(a_{im}\).

Порівняємо розподіл власних значень \(P(\lambda)\) для \(C\) з \(P_{rm}(\lambda)\). Для цього обчислимо власні значення \(\lambda_i\) матриці \(C\), причому \(\lambda_i\) впорядкуємо за зростанням (\(\lambda_{i+1} > \lambda_{i}\)). При дослідженнях зверніть увагу на присутність чіткої “великої частини” власних значень, що спадають у межах границь \([\lambda_{-}, \lambda_{+}]\) для \(P_{rm}(\lambda)\). Також зверніть увагу на відхилення для деяких найбільших і найменших власних значень отриманих за допомогою ТВМ.

Оскільки (8.6) є таким, що строго відповідає лише для \(L \to \infty\) і \(N \to \infty\), необхідно перевірити також відхилення від ідеального випадку, оскільки робота проводиться завжди зі скінченими рядами. При дослідженнях виявляється, що для кількох найбільших (найменших) власних значень ефект впливу скінчених величин \(L\) і \(N\) відсутній.

8.1.3 Розподіл власних векторів

Відхилення \(P(\lambda)\) від передбачення ТВМ \(P_{rm}(\lambda)\) свідчить про те, що ці відхилення також повинні відображатися в статистиці відповідних компонент власного вектора  [8]. Відповідно, у даній лабораторній ми будемо аналізувати розподіл компоненти власного вектора. Розподіл компонент \(\left\{ u_{l}^{k}; l=1,...,N \right\}\) власного вектора \(u^k\) випадкової кореляційної матриці \(R\) має відповідати розподілу Гауса з нульовим середнім та одиничною дисперсією:

\[ \rho_{rm}(u) = \frac{1}{\sqrt{2\pi}}\exp\left( -u^{2} \big/ 2 \right). \]

8.1.4 Обернене відношення участі

Вивчивши інтерпретацію найбільшого власного значення, що значно відхиляється від результатів ТВМ, зосередимось на власних значеннях, що залишаються. Відхилення розподілу компонентів власного вектора \(u_k\) від ТВМ Гаусового передбачення більш явне, коли відстань від верхньої границі ТВМ \(\lambda_k - \lambda_{+}\) збільшується. Оскільки близькість до \(\lambda_{+}\) збільшує ефекти хаотичності, визначаємо кількість компонентів, що беруть значну участь у кожному власному векторі, що, у свою чергу, відображає ступінь відхилення від ТВМ для розподілу компонентів власного вектора. Для цього використовується поняття оберненого відношення участі (ОВУ)  [1214], що часто застосовується в теорії локалізації. ОВУ власного вектора \(u_k\) визначається як

\[ I^{k} \equiv \sum_{l=1}^{N}\left[ u_{l}^{k} \right]^4, \tag{8.8}\]

де \(u_{l}^{k}\), \(l=1,...,N\) — компоненти власного вектора \(u^{k}\). Значення \(I^{k}\) може бути проілюстровано двома граничними випадками:

  • вектор з ідентичними компонентами \(u_{l}^{k} \equiv 1 \big/ \sqrt{N}\) має \(I^{k} = 1 \big/ N\);
  • вектор з одним компонентом \(u_{1}^{k}=1\) і нульовими іншими має \(I^{k}=1\).

Таким чином, ОВУ визначає кількість даних з числа компонентів власного вектора, що значно впливають на ринок, заданий системою часових рядів. Наявність векторів з великими значеннями \(I^{k}\) також виникає в теорії локалізації Андерсона. У контексті теорії локалізації часто знаходять “випадкову смугу матриць”, що містять узагальнені стани з маленьким \(I^{k}\) в більшій частині спектра власних значень, тоді як основні стани локалізовані і мають великі \(I^{k}\). Виявлення локалізованих станів для маленьких і великих власних значень матриці крос-кореляцій \(C\) нагадує про локалізацію Андерсона і припускає, що \(C\) може мати випадкову зону матричної структури.

8.2 Хід роботи

Імпортуємо необхідні бібліотеки:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import matplotlib.ticker as mticker
import pandas as pd
import yfinance as yf
import scienceplots
import requests
from tqdm import tqdm

np.seterr(divide='ignore', invalid='ignore')

%matplotlib inline

Визначаємо стиль рисунків:

plt.style.use(['science', 'notebook', 'grid']) # стиль, що використовуватиметься
                                               # для виведення рисунків

size = 16
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)              # оновлення стилю згідно налаштувань

Виконуємо парсинг та фільтрацію заголовків акцій компаній:

headers = {
    'authority': 'api.nasdaq.com',
    'accept': 'application/json, text/plain, */*',
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
    'origin': 'https://www.nasdaq.com',
    'sec-fetch-site': 'same-site',
    'sec-fetch-mode': 'cors',
    'sec-fetch-dest': 'empty',
    'referer': 'https://www.nasdaq.com/',
    'accept-language': 'en-US,en;q=0.9',
}

params = (
    ('tableonly', 'true'),
    ('limit', '25'),
    ('offset', '0'),
    ('download', 'true'),
)

r = requests.get('https://api.nasdaq.com/api/screener/stocks', headers=headers, params=params)
data = r.json()['data']
df = pd.DataFrame(data['rows'], columns=data['headers'])
df = df.dropna(subset={'marketCap'})
df = df[~df['symbol'].str.contains("\/|\.|\^")]
df.head()
symbol name lastsale netchange pctchange marketCap country ipoyear volume sector industry url
0 A Agilent Technologies Inc. Common Stock $145.17 -2.28 -1.546% 42542835578.00 United States 1999 835947 Industrials Biotechnology: Laboratory Analytical Instruments /market-activity/stocks/a
1 AA Alcoa Corporation Common Stock $31.94 0.54 1.72% 5735114141.00 United States 2016 3432603 Industrials Aluminum /market-activity/stocks/aa
2 AACG ATA Creativity Global American Depositary Shares $1.06 -0.02 -1.852% 33519500.00 China 2008 3530 Real Estate Other Consumer Services /market-activity/stocks/aacg
3 AACI Armada Acquisition Corp. I Common Stock $11.01 0.00 0.00% 0.00 United States 2021 1414 Finance Blank Checks /market-activity/stocks/aaci
4 AACIW Armada Acquisition Corp. I Warrant $0.0749 0.0018 2.462% 0.00 United States 2021 124 Finance Blank Checks /market-activity/stocks/aaciw

Фільтруємо та сортуємо заголовків акцій за їх капіталізацією:

def cust_filter(mkt_cap):
    if 'M' in mkt_cap:
        return float(mkt_cap[1:-1])
    elif 'B' in mkt_cap:
        return float(mkt_cap[1:-1]) * 1000
    elif mkt_cap == '':
        return 0.0
    else:
        return float(mkt_cap[1:]) / 1e6
    
df['marketCap'] = df['marketCap'].apply(cust_filter)
df = df.sort_values('marketCap', ascending=False)
df.head()
symbol name lastsale netchange pctchange marketCap country ipoyear volume sector industry url
2886 GOOG Alphabet Inc. Class C Capital Stock $151.05 -0.72 -0.474% 878004.650000 United States 2004 10560963 Technology Computer Software: Programming Data Processing /market-activity/stocks/goog
395 AMZN Amazon.com Inc. Common Stock $179.80 0.93 0.52% 867651.156122 United States 1997 20789293 Consumer Discretionary Catalog/Specialty Distribution /market-activity/stocks/amzn
2887 GOOGL Alphabet Inc. Class A Common Stock $149.99 -0.78 -0.517% 864825.670000 United States 2004 13005329 Technology Computer Software: Programming Data Processing /market-activity/stocks/googl
16 AAPL Apple Inc. Common Stock $171.60 -0.68 -0.395% 649826.779600 United States 1980 37196662 Technology Computer Manufacturing /market-activity/stocks/aapl
4662 NVDA NVIDIA Corporation Common Stock $954.34 11.45 1.214% 385850.000000 United States 1999 47962939 Technology Semiconductors /market-activity/stocks/nvda

Визначаємо найпередовіші акцій за їх капіталізацією:

top = 150
tickers_list = df.iloc[:top]['symbol'].tolist()
tickers_list[:10]
['GOOG', 'AMZN', 'GOOGL', 'AAPL', 'NVDA', 'META', 'MSFT', 'CRM', 'NVS', 'TMUS']

Зчитуємо дані з Yahoo Finance згідно створенного списку акцій. Вилучимо ціни закриття всіх акцій за період з 31 грудня 2001 року по 1 жовтня 2023 року:

start = "2001-12-31"
end = "2023-12-01"
data_init = yf.download(tickers_list, start, end)["Adj Close"]

xlabel = 'time, days'    # підпис по вісі Ох 

Далі нам потребується обрати фінансовий індекс для порівняння з розрахованими індикаторами. Далі надається список усіх доступних для нас акцій:

ylabel = 'AAPL'             # підпис по вісі Оу           

data_init.columns.values
array(['AAPL', 'ABBV', 'ABT', 'ACN', 'ADBE', 'AMAT', 'AMD', 'AMGN',
       'AMZN', 'ARM', 'ASML', 'AVGO', 'AXP', 'BA', 'BABA', 'BAC', 'BBD',
       'BDX', 'BHP', 'BKNG', 'BLK', 'BMO', 'BNH', 'C', 'CAT', 'CBRE',
       'CCZ', 'CEG', 'CHKP', 'CHT', 'CMCSA', 'CMG', 'COP', 'COST', 'CRM',
       'CVS', 'CVX', 'DE', 'DHR', 'DIS', 'DRI', 'DUKB', 'EBR', 'EL',
       'ELV', 'ETN', 'EXR', 'FDX', 'FMX', 'FTS', 'FTV', 'GE', 'GMAB',
       'GOOG', 'GOOGL', 'GS', 'GWW', 'HBAN', 'HD', 'HDB', 'HIG', 'HON',
       'HPQ', 'HSBC', 'HSY', 'IBM', 'INTC', 'INTU', 'ISRG', 'ITW', 'JBHT',
       'JNJ', 'JPM', 'KKR', 'KMI', 'KO', 'KOF', 'KVUE', 'LIN', 'LLY',
       'LOW', 'LPLA', 'LRCX', 'LYG', 'MA', 'MDT', 'META', 'MKL', 'MMC',
       'MRK', 'MS', 'MSFT', 'MU', 'MUFG', 'NEE', 'NFLX', 'NKE', 'NMR',
       'NOK', 'NOW', 'NVDA', 'NVO', 'NVS', 'ORCL', 'PDD', 'PEP', 'PFE',
       'PFG', 'PG', 'PGR', 'PLD', 'PM', 'PPL', 'QCOM', 'RACE', 'RSG',
       'RTX', 'RY', 'SAP', 'SCHW', 'SHEL', 'SHG', 'SNPS', 'SNY', 'SPG',
       'SPGI', 'STZ', 'SYK', 'T', 'TBB', 'TBC', 'TGT', 'TJX', 'TM', 'TMO',
       'TMUS', 'TSLA', 'TSM', 'TTE', 'TXN', 'UBER', 'UL', 'UNH', 'UNP',
       'UPS', 'V', 'VZ', 'WMT', 'WSM', 'XOM'], dtype=object)
perc = 5.0 
min_count =  int(((100-perc)/100)*data_init.shape[0] + 1)
data_init = data_init.dropna(axis=1, thresh=min_count)
data_init
Ticker AAPL ABT ACN ADBE AMAT AMD AMGN AMZN ASML AXP ... TTE TXN UL UNH UNP UPS VZ WMT WSM XOM
Date
2001-12-31 0.331081 14.912379 19.321152 15.452657 14.758861 15.860000 40.573254 0.541000 11.885590 22.767019 ... 10.828801 18.147404 8.532564 14.236670 9.228433 29.862459 14.652889 12.593814 14.009839 19.206507
2002-01-02 0.352246 14.936456 18.811560 15.845818 15.336689 16.389999 40.544498 0.548000 12.401443 22.939653 ... 10.930552 18.678860 8.558210 14.166265 9.163671 29.840557 14.970891 12.703233 13.405689 19.353123
2002-01-03 0.356478 14.949829 18.223030 16.462933 16.742651 19.370001 39.063622 0.595000 13.412239 23.304060 ... 10.987591 19.897335 8.483836 14.007341 9.500431 30.388485 15.467968 12.687912 13.562436 19.382444
2002-01-04 0.358142 14.923078 19.880976 17.866358 16.643274 20.000000 39.983780 0.612500 13.530748 24.116026 ... 11.038470 19.702888 8.386379 14.081776 9.714135 31.040512 15.625422 12.604758 13.742058 19.548613
2002-01-07 0.346199 14.818766 18.983816 17.995752 16.466602 19.980000 40.034107 0.617000 13.377389 24.039314 ... 10.904343 19.061247 8.296615 13.991246 9.702801 31.292574 15.529715 12.558800 13.768172 19.377560
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
2023-11-24 189.727905 102.375435 332.824677 619.429993 150.085632 122.309998 263.400879 146.740005 689.972473 163.890518 ... 67.166924 152.387482 47.501419 543.098999 221.883804 150.343994 36.789608 51.651596 182.729935 103.607109
2023-11-27 189.548126 102.216202 331.220490 619.270020 150.554840 122.650002 262.220093 147.729996 687.177368 163.511749 ... 67.088799 151.405243 47.402313 539.614685 217.501144 147.931061 36.730602 51.886585 182.710037 103.002724
2023-11-28 190.157349 101.569321 331.350037 623.320007 147.809494 122.010002 263.460419 147.029999 674.809021 165.126541 ... 67.352470 151.702881 47.471687 536.577087 218.866394 149.068314 36.848614 52.505505 183.207840 102.943283
2023-11-29 189.128662 103.131775 332.127197 617.390015 149.107300 123.849998 264.532043 146.320007 686.408752 166.890823 ... 66.268501 152.000534 47.104992 531.067627 219.103821 150.601105 37.133804 51.658218 186.314056 101.397644
2023-11-30 189.707916 103.788605 331.927948 611.010010 149.526596 121.160004 267.548492 146.089996 682.565491 170.220078 ... 66.454048 151.514374 47.273476 548.926025 222.863235 149.928665 37.694355 51.529133 186.712296 101.793961

5518 rows × 114 columns

data_init = data_init.dropna(axis=1)
data_init
Ticker AAPL ABT ACN ADBE AMAT AMD AMGN AMZN ASML AXP ... TTE TXN UL UNH UNP UPS VZ WMT WSM XOM
Date
2001-12-31 0.331081 14.912379 19.321152 15.452657 14.758861 15.860000 40.573254 0.541000 11.885590 22.767019 ... 10.828801 18.147404 8.532564 14.236670 9.228433 29.862459 14.652889 12.593814 14.009839 19.206507
2002-01-02 0.352246 14.936456 18.811560 15.845818 15.336689 16.389999 40.544498 0.548000 12.401443 22.939653 ... 10.930552 18.678860 8.558210 14.166265 9.163671 29.840557 14.970891 12.703233 13.405689 19.353123
2002-01-03 0.356478 14.949829 18.223030 16.462933 16.742651 19.370001 39.063622 0.595000 13.412239 23.304060 ... 10.987591 19.897335 8.483836 14.007341 9.500431 30.388485 15.467968 12.687912 13.562436 19.382444
2002-01-04 0.358142 14.923078 19.880976 17.866358 16.643274 20.000000 39.983780 0.612500 13.530748 24.116026 ... 11.038470 19.702888 8.386379 14.081776 9.714135 31.040512 15.625422 12.604758 13.742058 19.548613
2002-01-07 0.346199 14.818766 18.983816 17.995752 16.466602 19.980000 40.034107 0.617000 13.377389 24.039314 ... 10.904343 19.061247 8.296615 13.991246 9.702801 31.292574 15.529715 12.558800 13.768172 19.377560
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
2023-11-24 189.727905 102.375435 332.824677 619.429993 150.085632 122.309998 263.400879 146.740005 689.972473 163.890518 ... 67.166924 152.387482 47.501419 543.098999 221.883804 150.343994 36.789608 51.651596 182.729935 103.607109
2023-11-27 189.548126 102.216202 331.220490 619.270020 150.554840 122.650002 262.220093 147.729996 687.177368 163.511749 ... 67.088799 151.405243 47.402313 539.614685 217.501144 147.931061 36.730602 51.886585 182.710037 103.002724
2023-11-28 190.157349 101.569321 331.350037 623.320007 147.809494 122.010002 263.460419 147.029999 674.809021 165.126541 ... 67.352470 151.702881 47.471687 536.577087 218.866394 149.068314 36.848614 52.505505 183.207840 102.943283
2023-11-29 189.128662 103.131775 332.127197 617.390015 149.107300 123.849998 264.532043 146.320007 686.408752 166.890823 ... 66.268501 152.000534 47.104992 531.067627 219.103821 150.601105 37.133804 51.658218 186.314056 101.397644
2023-11-30 189.707916 103.788605 331.927948 611.010010 149.526596 121.160004 267.548492 146.089996 682.565491 170.220078 ... 66.454048 151.514374 47.273476 548.926025 222.863235 149.928665 37.694355 51.529133 186.712296 101.793961

5518 rows × 111 columns

data = data_init.T
data
Date 2001-12-31 2002-01-02 2002-01-03 2002-01-04 2002-01-07 2002-01-08 2002-01-09 2002-01-10 2002-01-11 2002-01-14 ... 2023-11-16 2023-11-17 2023-11-20 2023-11-21 2023-11-22 2023-11-24 2023-11-27 2023-11-28 2023-11-29 2023-11-30
Ticker
AAPL 0.331081 0.352246 0.356478 0.358142 0.346199 0.341815 0.327301 0.320952 0.318231 0.319743 ... 189.468246 189.448257 191.206009 190.397049 191.066193 189.727905 189.548126 190.157349 189.128662 189.707916
ABT 14.912379 14.936456 14.949829 14.923078 14.818766 14.722468 14.642225 14.775971 15.004192 15.006884 ... 99.777985 99.071396 100.713455 101.420052 102.206245 102.375435 102.216202 101.569321 103.131775 103.788605
ACN 19.321152 18.811560 18.223030 19.880976 18.983816 19.579529 19.787670 19.967098 18.998173 17.900053 ... 326.129120 326.637238 329.696075 329.058411 331.917969 332.824677 331.220490 331.350037 332.127197 331.927948
ADBE 15.452657 15.845818 16.462933 17.866358 17.995752 18.234634 18.727327 18.140078 17.931051 17.717054 ... 602.059998 602.659973 612.700012 610.989990 619.719971 619.429993 619.270020 623.320007 617.390015 611.010010
AMAT 14.758861 15.336689 16.742651 16.643274 16.466602 16.849388 17.180624 16.926674 16.565987 16.676403 ... 154.216721 148.020569 151.985306 148.678024 149.227097 150.085632 150.554840 147.809494 149.107300 149.526596
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
UPS 29.862459 29.840557 30.388485 31.040512 31.292574 31.352852 31.128199 31.150120 30.969296 30.700804 ... 145.280777 146.338913 147.911285 148.109070 149.078201 150.343994 147.931061 149.068314 150.601105 149.928665
VZ 14.652889 14.970891 15.467968 15.625422 15.529715 15.493931 15.207698 15.360151 15.462824 15.652602 ... 35.580006 35.629177 36.120884 36.632263 36.730602 36.789608 36.730602 36.848614 37.133804 37.694355
WMT 12.593814 12.703233 12.687912 12.604758 12.558800 12.657275 12.342160 12.473463 12.210861 12.202105 ... 51.644978 51.416603 51.400055 51.585403 51.191544 51.651596 51.886585 52.505505 51.658218 51.529133
WSM 14.009839 13.405689 13.562436 13.742058 13.768172 13.856347 13.539575 13.840023 13.405689 13.193413 ... 170.693298 178.060654 181.146957 179.145844 180.669083 182.729935 182.710037 183.207840 186.314056 186.712296
XOM 19.206507 19.353123 19.382444 19.548613 19.377560 19.402000 19.177185 19.250490 18.815535 18.883953 ... 101.516541 103.993515 103.537758 103.498123 103.052269 103.607109 103.002724 102.943283 101.397644 101.793961

111 rows × 5518 columns

8.2.1 Знаходження коефіцієнтів матриці крос-кореляцій

Перш за все нам потребується процедура для перетворення ряду до потрібного нам виду. Для цього визначимо функцію transformation(). З її допомогою для всього ряду та у віконній процедурі будемо рахувати прибутковості та всі необхідні індикатори.

def transformation(signal, ret_type):

    for_rmt = signal.copy()
    # Зважаючи на вид ряду, виконуємо
    # необхідні перетворення
    if ret_type == 1:       
        for_rmt.values
    elif ret_type == 2:
        for_rmt = for_rmt.diff(axis=1).values
    elif ret_type == 3:
        for_rmt = for_rmt.pct_change(axis=1).values
    elif ret_type == 4:
        for_rmt = for_rmt.pct_change(axis=1).dropna(axis=1).values
        for_rmt -= np.nanmean(for_rmt, axis=1, keepdims=True)
        for_rmt /= np.nanstd(for_rmt, axis=1, keepdims=True)
    elif ret_type == 5: 
        for_rmt = for_rmt.pct_change(axis=1).values
        for_rmt = for_rmt[~np.isnan(for_rmt).any(axis=1)]
        for_rmt -= np.mean(for_rmt, axis=1, keepdims=True)
        for_rmt /= np.std(for_rmt, axis=1, keepdims=True)
        for_rmt = np.abs(for_rmt)
    elif ret_type == 6:
        for_rmt = for_rmt.values
        for_rmt -= np.mean(for_rmt, axis=1, keepdims=True)
        for_rmt /= np.std(for_rmt, axis=1, keepdims=True)

    return for_rmt
type = 4

log_ret = transformation(data, type)
N, T = log_ret.shape

Будуємо матрицю кореляцій для матриці прибутковостей наших активів:

def calc_cross_corr(data):
    C = (1/T)*np.dot(data, data.T)
    di = np.diag_indices(data.shape[0])
    ccoef = np.ma.asarray(C)
    ccoef[di] = np.ma.masked 
    ccoef_flat = ccoef.compressed()

    return C, ccoef_flat
C, ccoef_flat = calc_cross_corr(log_ret)

І матрицю кореляцій для перемішаної матриці прибутковостей:

np.random.seed(1234)
random_stocks = np.random.normal(size=(N,T))

R, ccoef_flat_rand = calc_cross_corr(random_stocks)

Виводимо результат:

fig = plt.figure(figsize=(8, 6))
gs = gridspec.GridSpec(2, 2)

ax1 = fig.add_subplot(gs[0, 0])
im1 = ax1.imshow(C, cmap='hot', interpolation='nearest')
ax1.invert_yaxis()
fig.colorbar(im1, ax=ax1)

ax2 = fig.add_subplot(gs[0, 1])
im2 = ax2.imshow(R, cmap='hot', interpolation='nearest')
ax2.invert_yaxis()
fig.colorbar(im2, ax=ax2)

ax3 = fig.add_subplot(gs[1, :])
ax3.hist(ccoef_flat, bins='auto', density=True, label=r'$P^{init}(C_{ij})$')
ax3.hist(ccoef_flat_rand, bins='auto', density=True, label=r'$P^{rand}(C_{ij})$')
ax3.set_xlabel(r'$C_{ij}$')
ax3.set_ylabel(r'$P(C_{ij})$')
ax3.legend()

fig.align_labels()
plt.show();
Рис. 8.1: Карти полів кореляцій вихідної і перемішаної матриць. У нижній частині рисунку представлено порівняння розподілів значень коефіцієнтів кореляції вихідної та перемішаної матриць

На Рис. 8.1 видно, що розподіл коефіцієнтів кореляції для вихідної матриці значно відхиляється розподілу для випадкової матриці. Можна помітити, що \(P^{init}(C_{ij})\) представляється доволі розтягненим в діапазоні \(C_{ij}\in [0.1, 0.8]\), що вказує на те, що достатньо багато досліджуваних активів досить сильно відрізняють один від одного. Можна виокремити активи, які характеризуються досить значним ступенем лінійної залежності по відношенню до більності активів на протязі значного часу існування, а є такі активи, які проявляють лінійну залежність лише в конкретних ринкових умовах. Тим не менш, увесь ринок характеризується досить значним ступенем детермінованості.

8.2.2 Розподіл власних значень та векторів

def calc_lambd_eig(C):
    return np.linalg.eig(C)
lambdas, u = calc_lambd_eig(C)
lambdas_rand, u_rand = calc_lambd_eig(R)

Q = T/N

lambda_plus = 1 + 1/Q + 2*np.sqrt(1/Q)
lambda_minus = 1 - 1/Q - 2*np.sqrt(1/Q)

print("Верхня границя розподілу власних значень, що прогнозується ТВМ: ", lambda_plus)
print("Нижня границя розподілу власних значень, що прогнозується ТВМ: ", lambda_minus)
Верхня границя розподілу власних значень, що прогнозується ТВМ:  1.3038069950128888
Нижня границя розподілу власних значень, що прогнозується ТВМ:  0.6961930049871112

Генеруємо 1000 випадкових значень в діапазоні від \(\lambda_{-}\) до \(\lambda_{+}\), і генеруємо \(P_{rm}(\lambda)\).

lambda_random = np.linspace(lambda_minus, lambda_plus, 1000)
P_rm = (Q/(2*np.pi))*np.sqrt((lambda_plus-lambda_random)*(lambda_random-lambda_minus))/lambda_random

Виводимо результат:

fig, ax = plt.subplots(2, 1, figsize=(8, 6))
ax[0].hist(lambdas, bins=50, density=True, color="skyblue", label='$P^{init}_{\it{empiric}}$')
ax[0].hist(lambdas_rand, bins='auto', density=True, color="red", label='$P^{rand}_{\it{empiric}}$')
ax[0].set_xlabel('$\lambda_{i}$')
ax[0].set_ylabel('$P(\lambda_{i})$')
ax[0].text(20, 0.5, '$\lambda_{max}=$'+f'{lambdas.max():.2f}', ha='center', va='center')
ax[0].set_yscale('log')
ax[0].legend()

ax[1].hist(lambdas, bins='auto', density=True, color="skyblue", label='$P^{init}_{\it{empiric}}$')
ax[1].hist(lambdas_rand, bins='auto', density=True, color="red", label='$P^{rand}_{\it{empiric}}$')
ax[1].set_xlabel('$\lambda_{i}$')
ax[1].set_ylabel('$P(\lambda_{i})$')
ax[1].set_xlim(lambda_minus-3*np.std(lambdas_rand), lambda_plus+3*np.std(lambdas_rand))
ax[1].scatter(lambda_random, P_rm, label='$P_{\it{RMT}}$', color='green')
ax[1].legend()

fig.tight_layout()
plt.show();
Рис. 8.2: Щільність розподілу ймовірностей для матриці прибутковостей фінансових активів \(P_{empiric}^{init}\) та випадкової матриці \(P_{empiric}^{rand}\). Другий рисунок представляє щільність розподілу, що прогнозується ТВМ \((P_{RMT})\)

На Рис. 8.2 видно, що розподіл \(\lambda_i\) значно відхиляється від тієї частки, що відповідає випадковій матриці крос-кореляцій (\(P^{rand}_{empiric}\)). Для вихідного розподілу видно, що \(\lambda_{max}\) значно відхиляється від передбачення ТВМ. Для \(P^{init}_{empiric}\) знаходяться і такі власні значення, що менші за ті, що знаходяться на нижній границі випадкової матриці. Загалом, така закономірність вказує на те, що на ринку є один або декілька індексів, що регулюють динаміку всього ринку.

На Рис. 8.3 представлено щільність розподілу ймовірностей компонент власних векторів вихідної матриці прибутковостей і випадкової матриці при \(\lambda_{1}\), \(\lambda_{30}\) та \(\lambda_{70}\).

fig, ax = plt.subplots(3, 1, figsize=(8, 6), sharex=True, sharey=True)
ax[0].hist(u[:, 0], bins=50, density=True, label=r'$\rho(u)_{empiric}^{init}$')
ax[0].hist(u_rand[:, 0], bins=50, density=True, alpha=0.7, label=r'$\rho(u)_{empiric}^{rand}$')
ax[0].set_xlabel('$u_{1}$')
ax[0].set_ylabel('$P(u)$')
ax[0].set_title('Компоненти власного вектора при $ \lambda_{1} $')
ax[0].legend()
ax[0].set_yscale('log')

ax[1].hist(u[:, 30], bins=50, density=True, label=r'$\rho(u)_{empiric}^{init}$')
ax[1].hist(u_rand[:, 30], bins=50, density=True, alpha=0.7, label=r'$\rho(u)_{empiric}^{rand}$')
ax[1].set_xlabel('$u_{30}$')
ax[1].set_ylabel('$P(u)$')
ax[1].set_title('Компоненти власного вектора при $ \lambda_{30} $')
ax[1].legend()
ax[1].set_yscale('log')

ax[2].hist(u[:, 70], bins=50, density=True, label=r'$\rho(u)_{empiric}^{init}$')
ax[2].hist(u_rand[:, 70], bins=50, density=True, alpha=0.7, label=r'$\rho(u)_{empiric}^{rand}$')
ax[2].set_xlabel('$u_{70}$')
ax[2].set_ylabel('$P(u)$')
ax[2].set_title('Компоненти власного вектора при $ \lambda_{70} $')
ax[2].legend()
ax[2].set_yscale('log')

fig.tight_layout()
plt.show();
Рис. 8.3: Щільність розподілу ймовірностей компонент власних векторів вихідної матриці прибутковостей і випадкової матриці при \(\lambda_1\), \(\lambda_{30}\) та \(\lambda_{70}\)

На Рис. 8.3 видно, що компоненти власного вектора при найбільшому власному значень значно відхиляються від передбачення ТВМ, що вказує на значний ступінь впливовості індексу, який відповідає \(\lambda_{1}\). При \(\lambda_{30}\) та \(\lambda_{70}\) розподіл компонент власних векторів вихідних значень починає збігатися до розподілу випадкових значень, що говорить про незначний вплив індексів, яким відповідають ці власні значення. Оскільки розподіл компонент їх векторів близький до ТВМ, можна сказати, що вони вносять найбільший шум у динаміку ринку.

На Рис. 8.4 представлено ОВУ для вихідної матриці крос-кореляцій та випадкової.

8.2.3 Обернене відношення участі

def calc_ipr(u):
    return np.sum(u**4, axis=0)
IPR = calc_ipr(u)
IPR_rand = calc_ipr(u_rand)
fig, ax = plt.subplots(1, 1)

ax.scatter(lambdas, IPR, color='green', label=r'$IPR_{init}$', s=10**2, marker='x')
ax.scatter(lambdas_rand, IPR_rand, color='red', label=r'$IPR_{rand}$', s=10**2, marker='x')
ax.set_xlabel(r'$\lambda_{i}$')
ax.set_ylabel(r'$IPR$')
ax.set_xscale('log')
ax.legend()

fig.tight_layout()
plt.show(); 
Рис. 8.4: ОВУ для вихідної та випадкової матриць крос-кореляцій

На Рис. 8.4 видно, що ОВУ для, наприклад, випадкової матриці концентрується в межах \(\lambda_i \approx 10^0\). Для даних, що значно відхиляються від випадкових, ОВУ має довгі хвости, які виходять далеко за межі передбачень ТВМ. Для набільшого власного вектора ОВУ локалізується далеко в правому хвості розподілу. Також видно, що деякі власні значення мають \(IRP \approx 0.25\). Це говорить про те, що компоненти деяких векторів розподілені асиметрично, що може вказувати на деяких ступінь впливу фондових індексів, яким властиві дані вектори.

8.2.4 Коефіцієнт поглинання

Цікаво буде буде порівняти максимальне власне значення \(\lambda_{max}\) з середнім значенням коефіцієнта кореляції і так званим коефіцієнтом поглинання (absorption ratio, AR), який є кумулятивною мірою ризику:

\[ AR = \sum_{k=1}^{n}\lambda_k \bigg/ \sum_{k=1}^{N}\lambda_k. \tag{8.9}\]

\(AR\) вказує, яку частину загальної варіації описують \(n\) із загальної кількості \(N\) власних значень.

Щоб вирішити, який власний вектор можна відкинути для розрахунків у чисельнику формули вище, не втрачаючи занадто багато інформації, нам потрібно перевірити відповідні власні значення: власні вектори з найменшими власними значеннями є найменш інформативними, тому їх і можна відсікти.

Загальний підхід полягає в упорядкуванні власних значень від найвищого до найнижчого.

Після сортування власних значень постає питання: “яку кількість найбільш інформативних власних компонент треба вибрати для розрахунку коефіцієнту поглинання?”. Корисною мірою є так звана “врахована (пояснена) дисперсія”, яка може бути обчислена за власними значеннями. Пояснена дисперсія говорить нам, яку частину інформації (дисперсії) можна віднести до кожної із головних компонент.

Визначимо слідуючу функцію для коефіцієнту поглинання:

def ar(lambdas, proc):
    
    # сортуємо власні значення у спадному порядку
    sorted_lambdas = np.sort(lambdas)[::-1]

    # розраховуємо кумулятивну варіацію 
    cumulative_variance = np.cumsum(sorted_lambdas) / np.sum(sorted_lambdas)

    # знаходимо індекс, де кумулятивна варіація перетинає "proc"
    index_percent = np.argmax(cumulative_variance >= proc) + 1

    # виділяємо верхні власні значення, які описують встановлений відсоток даних
    selected_lambdas = sorted_lambdas[:index_percent]

    # повертаємо коефіцієнт поглинання та кількість значень, що пояснюють обраний відсоток варіації
    return np.sum(selected_lambdas)/np.sum(sorted_lambdas), len(selected_lambdas)

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

proc_variance = np.linspace(0, 0.99, 100)
ar_init = np.array([ar(lambdas, proc)[0] for proc in proc_variance])
ar_rand = np.array([ar(lambdas_rand, proc)[0] for proc in proc_variance])

lambdas_init_cnt = np.array([ar(lambdas, proc)[1] for proc in proc_variance])
lambdas_rand_cnt = np.array([ar(lambdas_rand, proc)[1] for proc in proc_variance])

fig, ax = plt.subplots(1, 2, figsize=(8, 6))

ax[0].plot(proc_variance, ar_init, color='green', label=r'$AR_{init}$')
ax[0].plot(proc_variance, ar_rand, color='red', label=r'$AR_{rand}$')
ax[0].set_xlabel('Відсоток варіації\n(a)')
ax[0].set_ylabel('$AR$')
ax[0].legend()

ax[1].plot(proc_variance, lambdas_init_cnt, color='green', label=r'$\#\lambda_{i}^{init}$')
ax[1].plot(proc_variance, lambdas_rand_cnt, color='red', label=r'$\#\lambda_{i}^{rand}$')
ax[1].set_xlabel('Відсоток варіації\n(b)')
ax[1].set_ylabel(r'Кі-ть $\lambda_i$')
ax[1].legend()

fig.tight_layout()
plt.show();
Рис. 8.5: Залежність коефіцієнта поглинання від різних значень поясненої варіації власними значеннями матриць кореляції вихідних прибутковостей та випадкових (a). Залежність кількості поясненої варіації від кількості власних значень, що пояснюють такий відсоток (b)

Як ми можемо бачити на Рис. 8.5, коефіцієнт поглинання для вихідної матриці кореляцій залишається сталим при перших 40% варіації. На рисунку справа видно, що лише одного власного значення достатньо для опису майже половини всього ринку. Для випадкової матриці кількість власних значень необхідних для опису системи зростає прямо пропоційно відсотку варіації. Отже, для нашої задачі при розрахунку коефіцієнту поглинання достатньо буде взяти до 40% варіації при врахуванні кількості необхідних власних значень у чисельнику (8.9).

8.2.5 Віконна процедура

Перш ніж розпочинати розрахунки визначимо функцію для побудови графіків. Вона буде виводити один із часових рядів досліджуваної бази акцій компаній та розраховані індикатори. Ми будемо виводити як двовимірні, так і тривимірні графіки.

def plot_2d(time_ser_index, 
            time_ser_values,
            y1_values,  
            time_ser_label, 
            y1_label, 
            x_label,
            file_name, 
            clr="magenta"):

    fig, ax = plt.subplots(1, 1)

    ax2 = ax.twinx()

    ax2.spines.right.set_position(("axes", 1.03))

    p1, = ax.plot(time_ser_index, 
                time_ser_values, 
                "b-", 
                label=fr"{time_ser_label}")
    p2, = ax2.plot(time_ser_index, 
                y1_values, color=clr, label=y1_label)

    ax.set_xlabel(x_label)
    ax.set_ylabel(f"{y1_label}")

    ax.yaxis.label.set_color(p1.get_color())
    ax2.yaxis.label.set_color(p2.get_color())

    tkw = dict(size=4, width=1.5)
    ax.tick_params(axis='x', rotation=45, **tkw)
    ax.tick_params(axis='y', colors=p1.get_color(), **tkw)
    ax2.tick_params(axis='y', colors=p2.get_color(), **tkw)
    ax2.legend(handles=[p1, p2])

    plt.savefig(file_name + ".jpg")
        
    plt.show();
window = 250        # розмір вікна
tstep = 1           # крок вікна
ret_type = 4        # вид ряду: 
                    # 1 - вихідний, 
                    # 2 - детрендований (різниця між теп. значенням та попереднім)
                    # 3 - прибутковості звичайні, 
                    # 4 - стандартизовані прибутковості, 
                    # 5 - абсолютні значення (волатильності)
                    # 6 - стандартизований ряд

length = data.shape[1]
num_bins = 50

mean_corr_init = []
mean_corr_rand = []

C_vals_init = [] 
C_vals_rand = []

C_hist_vals_init = []
C_hist_vals_rand = []

u_hist_vals_init = []
u_hist_vals_rand = []

lambdas_vals_init = []
lambdas_vals_rand = []

lambdas_vals_max_init = []
lambdas_vals_max_rand = []

lambdas_hist_vals_init = []
lambdas_hist_vals_rand = []

ipr_vals_init = []
ipr_vals_rand = []

absorb_vals_init = []
absorb_vals_rand = []
for i in tqdm(range(0,length-window,tstep)):
    
    # відбираємо фрагменти
    fragm = data.iloc[:, i:i+window].copy()  

    # виконуємо процедуру трансформації ряду 
    fragm = transformation(fragm, ret_type)
    fragm = fragm[~np.isnan(fragm).any(axis=1)]

    # генеруємо випадковий фрагмент
    fragm_random = np.random.normal(size=(fragm.shape[0], fragm.shape[1]))

    # розраховуємо крос-кореляції
    C_fragm, ccoef_flat_fragm = calc_cross_corr(fragm)
    C_fragm_random, ccoef_flat_fragm_random = calc_cross_corr(fragm_random)

    # розраховуємо гістограму крос-кореляцій
    C_fragm_hist = np.histogram(ccoef_flat_fragm, bins=num_bins, density=True)[0]
    C_fragm_hist_rand = np.histogram(ccoef_flat_fragm_random, bins=num_bins, density=True)[0]

    # розрахунок середнього значення коефіцієнта кореляції
    mean_correlation = np.mean(ccoef_flat_fragm)
    mean_correlation_random = np.mean(ccoef_flat_fragm_random)

    # розрахунок власних значень та векторів
    lambdas_fragm, u_fragm = calc_lambd_eig(C_fragm)
    lambdas_fragm_random, u_fragm_random = calc_lambd_eig(C_fragm_random)

    # розраховуємо гістограму власних значень
    lambdas_fragm_hist = np.histogram(lambdas_fragm, bins=num_bins, density=True)[0]
    u_fragm_hist_init = np.histogram(u_fragm, bins=num_bins, density=True)[0]
    lambdas_fragm_hist_rand = np.histogram(lambdas_fragm_random, bins=num_bins, density=True)[0]
    u_fragm_hist_rand = np.histogram(u_fragm_random, bins=num_bins, density=True)[0]

    # отримання максимального власного значення
    lambdas_fragm_max = lambdas_fragm.max()
    lambdas_fragm_max_random = lambdas_fragm_random.max()

    # отримання оберненого відношення участі
    ipr_fragm = calc_ipr(u_fragm)
    ipr_random = calc_ipr(u_fragm_random)

    # розрахунок коефіцієнта поглинання 
    absorb_init = ar(lambdas_fragm, 0.05)[0]
    absorb_rand = ar(lambdas_fragm_random, 0.05)[0]

    # додаємо індикатори до масивів
    mean_corr_init.append(mean_correlation)
    mean_corr_rand.append(mean_correlation_random)

    C_vals_init.append(ccoef_flat_fragm)
    C_vals_rand.append(ccoef_flat_fragm_random)

    C_hist_vals_init.append(C_fragm_hist)
    C_hist_vals_rand.append(C_fragm_hist_rand)
    lambdas_hist_vals_init.append(lambdas_fragm_hist)
    lambdas_hist_vals_rand.append(lambdas_fragm_hist_rand)
    u_hist_vals_init.append(u_fragm_hist_init)
    u_hist_vals_rand.append(u_fragm_hist_rand)

    lambdas_vals_init.append(lambdas_fragm)
    lambdas_vals_rand.append(lambdas_fragm_random)

    lambdas_vals_max_init.append(lambdas_fragm_max)
    lambdas_vals_max_rand.append(lambdas_fragm_max_random)

    ipr_vals_init.append(ipr_fragm)
    ipr_vals_rand.append(ipr_random)

    absorb_vals_init.append(absorb_init)
    absorb_vals_rand.append(absorb_rand)
100%|██████████| 5268/5268 [04:04<00:00, 21.51it/s]
ind_names = ['avg_corr_init', 'lambda_max', 'ar']

indicators = [mean_corr_init, lambdas_vals_max_init, absorb_vals_init]

measure_labels = [r'$\langle C \rangle_{init}$', r'$\lambda_{max}$', r'$AR$']

for i in range(len(ind_names)):
    name = f"RMT_{ind_names[i]}_symbol={ylabel}_wind={window}_step={tstep}_seriestype={ret_type}"
    np.savetxt(name + ".txt", indicators[i])

8.2.5.1 Середній коефіцієнт крос-кореляцій

file_name = f"avg_corr_symbol={ylabel}_wind={window}_step={tstep}_seriestype={ret_type}"

plot_2d(data_init.loc[:, ylabel].index[window:length:tstep],
        data_init.loc[:, ylabel].values[window:length:tstep],
        mean_corr_init, 
        ylabel,
        measure_labels[0],
        xlabel,
        file_name,
        clr="magenta")
Рис. 8.6: Динаміка індексу цін акцій компанії Apple та середнього показника крос-кореляцій прибутковостей досліджуваних акцій

На Рис. 8.6 видно, що середній ринковий ступінь крос-кореляцій зростає у (перед-)кризові періоди, що вказує на зростання самоорганізованості ринку. Усі індекси синхронізовано починають “відповідати” на зовнішні чинники, які змушують трейдерів колективно все відкуповувати або розпродавати.

На Рис. 8.7 представлений той самий показник, але у тривимірному просторі.

Y = np.linspace(-1, 1, num_bins)

X = np.arange(window, length, tstep)
X = np.expand_dims(X, axis=1)
X = np.repeat(a=X, repeats=Y.shape[0], axis=1)

Z = np.array(C_hist_vals_init)

fig, ax = plt.subplots(subplot_kw={"projection": "3d"}, figsize=(8, 6))

surf = ax.plot_surface(X, Y, Z, cmap='hot', rstride=2, cstride=2, linewidth=0)

ax.set_xlabel(xlabel, fontsize=16, labelpad=15)
ax.set_ylabel(r"$C_{ij}$", fontsize=16, labelpad=15)
ax.set_zlabel(r"$P(C)$", fontsize=16, labelpad=15)
ax.tick_params(axis='both', which='major', labelsize=16, pad=5)

ax.view_init(60, 140)

fig.tight_layout()

plt.savefig(f"RMT_3D_P(C)_wind={window}_step={tstep}_seriestype={ret_type}.jpg", bbox_inches="tight")

plt.show();
Рис. 8.7: Віконна функція щільності розподілу коефіцієнтів кореляції вихідної матриці фінансових активів

8.2.5.2 Максимальне значення \(\lambda\)

На наступному рисунку (Рис. 8.8) представлено порівняльну динаміку індексу цін акцій компанії Apple та показника \(\lambda_{max}\).

file_name = f"lambda_max_symbol={ylabel}_wind={window}_step={tstep}_seriestype={ret_type}"

plot_2d(data_init.loc[:, ylabel].index[window:length:tstep],
        data_init.loc[:, ylabel].values[window:length:tstep],
        lambdas_vals_max_init, 
        ylabel,
        measure_labels[1],
        xlabel,
        file_name,
        clr="red")
Рис. 8.8: Динаміка індексу цін акцій компанії Apple та показника \(\lambda_{max}\)

Рис. 8.8 показує, що \(\lambda_{max}\) у схожий спосіб із \(\langle C \rangle_{init}\). Із динаміки даного показника можна зробити висновок, що серед усіх індексів найбільш впливовим є індекс, якому характерно \(\lambda_{max}\). Усі інші індекси, власне значення яких знаходиться в межах теоретичного передбачення ТВМ, вносять лише шумову інформацію в загальну динаміку ринку і реагують на всі інші збурення на фондовому ринку, слідуючи закономірностям найбільш капіталізованих індексів.

8.2.5.3 Обернене відношення участі

На Рис. 8.9 представлено тривимірну віконну динаміка показника оберненого відношення участі

def log_tick_formatter(val, pos=None):
    return f"$10^{{{int(val)}}}$"  

Y = np.array(lambdas_vals_init)

X = np.arange(window, length, tstep)
X = np.expand_dims(X, axis=1)
X = np.repeat(a=X, repeats=Y.shape[1], axis=1)

Z = np.array(ipr_vals_init)

fig, ax = plt.subplots(subplot_kw={"projection": "3d"}, figsize=(8, 6))

surf = ax.plot_surface(X, np.log(Y), Z, cmap='magma', rstride=2, cstride=2, linewidth=0.5)

ax.set_xlabel(xlabel, fontsize=16, labelpad=15)
ax.set_ylabel(r"$\lambda_i$", fontsize=16, labelpad=15)
ax.set_zlabel(r"$IPR$", fontsize=16, labelpad=15)
ax.tick_params(axis='both', which='major', labelsize=16, pad=5)

ax.yaxis.set_major_formatter(mticker.FuncFormatter(log_tick_formatter))
ax.yaxis.set_major_locator(mticker.MaxNLocator(integer=True))

fig.tight_layout()

plt.savefig(f"RMT_3D_IPR_wind={window}_step={tstep}_seriestype={ret_type}.jpg", bbox_inches="tight")

plt.show();
Рис. 8.9: Тривимірна віконна динаміка показника оберненого відношення участі

ОВУ на Рис. 8.9 характеризується значним зростанням у кризові періоди, у той час як для періодів релаксації цей показник залишається на рівні нуля.

8.2.5.4 Коефіцієнт поглинання

На Рис. 8.10 представлено порівняльну динаміку індексу цін акцій компаній Apple та коефіцієнту поглинання.

file_name = f"ar_symbol={ylabel}_wind={window}_step={tstep}_seriestype={ret_type}"

plot_2d(data_init.loc[:, ylabel].index[window:length:tstep],
        data_init.loc[:, ylabel].values[window:length:tstep],
        absorb_vals_init, 
        ylabel,
        measure_labels[2],
        xlabel,
        file_name,
        clr="black")
Рис. 8.10: Динаміка індексу цін акцій компаній Apple та коефіцієнту поглинання

На Рис. 8.10 видно, що \(AR\) зростає під час крахів 2008, 2015-2016, 2021 і 2023 року. Це говорить про те, що в кризові періоди зростає активність одного або декілька індексів, які стають рушієм для усього фондового ринку.

8.3 Висновок

Таким чином, при наявності сукупності часових рядів, що є даними діяльності економічних об’єктів однієї області, можна провести дослідження стосовно структури вказаної області та взаємодії об’єктів всередині неї. Дослідження проводяться на основі теорії випадкових матриць, що дозволяє отримувати інформацію шляхом аналізу матриці крос-кореляцій, побудованої для сукупної бази економічних об’єктів.

8.4 Завдання для самостійної роботи

Оберіть певний фондовий індекс і проведіть дослідження крахових подій для ринку, що він представляє за допомогою теорії випадкових матриць.

8.5 Контрольні запитання

  1. Поясніть основну ідею теорії випадкових матриць
  2. Про що свідчить відмінність кореляційних і спектральних властивостей матриці даних і випадкової?
  3. Дослідіть, як змінюється розподіл власних значень у випадках:
    • \(\lambda_{-} < \lambda < \lambda_{+}\);
    • \(\lambda > \lambda_{+}\);
    • \(\lambda < \lambda_{-}\).
  4. Порівняйте кольорову карту поля взаємних кореляцій випадкової матриці і заданої. Зробіть висновки