Групиране на акции от индекса S&P500 с помощта на неконтролирано машинно обучение.

Въведение

В предишната публикация в блога въведохме концепцията за бета, изградихме функцията за изчисляването й и я обяснихме като инструмент за диверсификация на портфолио. В тази публикация ще изчислим бета за всяка акция в индекса S&P500 и впоследствие ще ги групираме въз основа на техните стойности. Ще използваме Agglomerative Clustering, един от алгоритмите за клъстериране. Целта му е да ни помогне да групираме акции с подобни бета версии.

След това можем да проектираме портфолио, като изберем комбинация от акции от всяка група с различни бета версии.

Разбира се, това е опростена форма на изграждане на портфолио, но бета версията може да бъде един от няколкото фактора, които трябва да имате предвид.

Ето очертанията на тази публикация:

  1. Бързо опресняване на бета + Python код от предишната публикация
  2. Теоретично обяснение на агломеративното групиране
  3. Практическо обяснение на агломеративното групиране, бета групиране
  4. Бърза дискусия относно съставянето на примерен портфейл от акции

Отказ от отговорност: Информацията, съдържаща се в тази публикация в блога, е само за образователни и развлекателни цели. Това също не е инвестиционен съвет. Всички възгледи, изразени в този блог, са мои собствени и не представляват мненията на каквото и да е образувание, с което съм бил, сега съм или ще бъда свързан.

Бета и нейната формула

За да обобщим, бета измерва как една акция се колебае пропорционално на общите пазарни промени. Той описва представянето на акцията по отношение на съответния индекс.

Бета от 1 предполага, че акциите имат линейна връзка с пазара; под 1 предполага, че акциите са по-малко променливи от пазара; и над 1 показва, че акциите са по-волатилни от целия пазар.

Бета се изчислява, както следва:

Бета = (съотношение между възвръщаемостта на акциите и възвръщаемостта на индекса) * (Стандартното отклонение на възвръщаемостта на акциите / стандартното отклонение на възвръщаемостта на индекса)

Да приемем, че бета стойността на случаен запас е 0,80, за да предоставим използваем пример. Това показва, че произволната акция е с 20% по-малко променлива от пазара или индекса, към който принадлежи.

Кодът на Python, който изчислява бета, може да бъде намерен в Приложението на тази публикация в блога.

Накрая, моля, имайте предвид, че кодирам тази част, докато пиша тази публикация, така че нямам представа какви ще бъдат крайните резултати. Ще видим в края на този материал дали този метод е полезен.

Агломеративно групиране

Алгоритъмът за агломеративно групиране е един от йерархичните методи за групиране на данни. Алгоритъмът използва подход отдолу нагоре, при който всяка отделна точка от данни започва като собствен клъстер и след това тези клъстери се групират заедно, за да образуват по-големи клъстери въз основа на техните прилики. Мярката за разстояние и критерият за свързване определят колко сходни са клъстерите един спрямо друг.

Сходството или различието между индивидуалните точки се определя от мярката за разстояние, докато разстоянието между групитесе определя от критерия за връзка.

За да разширим, показателят за разстояние е математическа функция, която приема две наблюдения като вход и извежда скаларно число, което представлява разделянето или сходството между двете точки от данни. Евклидовото разстояние, разстоянието Манхатън, косинусното подобие, подобието на Жакард и т.н. са някои примери за редовно използвани показатели за разстояние. В нашия практически пример евклидовото разстояние е мярката за разстояние по подразбиране в агломеративното групиране. Евклидовото разстояние се извлича от теоремата на Питагор. Изведената формула е следната:

където (x1, y1) са координатите на една точка, (x2, y2) са координатите на втората точка и dе разстоянието между тези две точки.

По отношение на критерия за свързване — той се използва, за да се определи дали два клъстера трябва да бъдат обединени на всяка фаза от йерархичния процес на клъстериране. Критерият за свързване може да бъде единична връзка, пълна връзка, средна връзка, връзка на отделението и т.н. В нашия пример ще използваме връзката „отделение“, за да получим близостта на клъстерите. Методът на Уорд измерва сумата на грешките на квадратите между клъстерите и се опитва да ги сведе до минимум. Основно се стреми да минимизира вариацията в рамките на клъстерите след обединяването им. В началото сумата на квадратите е нула, но расте с обединяването. Методът на Уорд се опитва да запази сбора на квадратите много малък.

Методът на Уорд взема евклидовите разстояния и ги комбинира в клъстери. Следователно критерият за свързване зависи от разстоянията. С други думи, трябва да има разстояние, за да се определят връзките.

Определяне на брой клъстери

Има различни начини за определяне на оптимален брой клъстери, като използване на дендрограма, метод на силуета, метод на лакътя и т.н. В този пример ще използваме коефициента на силуета, който измерва сходството на една точка с нейния собствен клъстер в сравнение с други клъстери. Това се изчислява, както следва:

(b — a) / max(a, b)

където a е средното разстояние между точката и всички други точки в нейния клъстер и b е средното разстояние между точката и всички точки в най-близкия клъстер. Това води до коефициент на силует, който е между 0 и 1 и представлява колко подобни са два клъстера един на друг. Висок коефициент на силует означава, че две групи са по-сходни от всяка друга група във вашия набор от данни - и следователно може да се считат за един клъстер. Нисък коефициент на силует означава, че две групи са по-малко сходни от която и да е друга група във вашия набор от данни — и следователно може вместо това да се разглеждат като отделни.

Практически пример: Клъстериране на бета версии

Сега ще приложим всичко, което научихме досега. Библиотеката scikit-learn на Python може да се използва за прилагане на агломеративно клъстериране. Но преди да започнем процеса на клъстериране, първо трябва да съставим списък на акциите, които съставляват индекса S&P 500 и да определим индивидуалната бета версия на всяка акция.

Но дори преди това нека импортираме всички необходими библиотеки за останалата част от този блог.

import yfinance as yf
import pandas as pd
import numpy as np
from sklearn.cluster import AgglomerativeClustering
from sklearn.metrics import silhouette_score
import matplotlib.pyplot as plt
import seaborn as sns

Сега, нека получим символите на компаниите от индекса S&P500, като изтрием страницата на Wikipedia, https://en.wikipedia.org/wiki/List_of_S%26P_500_companies. Изглежда, че е актуален. Ще използваме функцията pd.read_html()за изчерпване на страницата. Ще преобразуваме изведената рамка от данни в списък, за да можем по-късно да я включим в нашата бета функция.

Моля, имайте предвид, че има няколко символа, които трябва да премахнем, тъй като yfinance няма данни за тях и факт е, че те може да са били премахнати: BRK.B и BF.B.

companies=pd.read_html('https://en.wikipedia.org/wiki/List_of_S%26P_500_companies')
table  = companies[0]
df = table[table["Symbol"].str.contains("BRK.B|BF.B") == False]
ticker_list = df['Symbol'].to_list()
ticker_list[:10]

#returns 
['MMM', 'AOS', 'ABT', 'ABBV', 'ACN', 'ATVI', 'ADM', 'ADBE', 'ADP', 'AAP']

Сега сме готови да използваме нашата бета функция за изчисляване на бета за всяка акция в ticker_list. Кодът за функцията бетае в Приложението на тази публикация в блога.

#the full code for the 'betas' function show in the Appendix of this post
betas = betas('^GSPC', ticker_list, '2010-01-01', '2023-01-27')

Ето първите 10 реда от бета рамката с данни:

Нека набързо да разгледаме някои статистики с метода describe():

Както можем да забележим, бета стойностите варират между 0,389 и 1,910, доста широк диапазон.

Въпреки това, 50% от бетата са равни или под 1,037 и само 25% от акциите имат данни между 0,853 и 1,037. Така че може да се очаква, че клъстерирането може да не е толкова лесно.

Методът на силуета: намиране на оптималния брой клъстери

Преди клъстерирането трябва да се определи идеалният брой клъстери, като се използва силуетният метод, обсъден по-горе. Ще тестваме набор от клъстери от 2 до 10 и ще съберем техните силуетни резултати, ще вземем средната им стойност и ще ги начертаем на графика.

#assigning Beta column to X
X = betas[['Beta']]

#testing number of cluster from 2 to 10 and collecting the silhouette scores
range_n_clusters = [2, 3, 4, 5, 6, 7, 8, 9, 10]
scores = []
for n_clusters in range_n_clusters:
    agglom = AgglomerativeClustering(n_clusters=n_clusters)
    agglom.fit(X)
    labels = agglom.labels_
    scores.append(silhouette_score(X, labels))

#looking at the scores, and their average/mean
scores
average = sum(scores)/len(scores)
average

#returns, 0.5132650198111744

#graphing 

plt.plot(range_n_clusters, scores)
plt.xlabel('Number of Clusters')
plt.ylabel('Silhouette Score')
plt.show()

Идеалният брой клъстери изглежда е една от трите опции: n_clusters=2, n_clusters=4 или n_clusters=10. Числото n_cluster от 10 е малко повече от средната стойност на всички резултати, поради което също си струва да се проучи. Може да е трудно да се избере най-добрият брой клъстери.

Има потенциално много начини, по които можем да го направим. В тази публикация в блога ще изберем да визуализираме клъстерите въз основа на оптималните кандидати. За всеки потенциален оптимален брой клъстери ще създадем диаграма на разсейване. След това ще определим коя диаграма на разсейване показва най-ясно разделените и различни клъстери. Ще получим и брой наблюдения на клъстер с всяка опция.

for n_clusters in optimal_numbers:
    model = AgglomerativeClustering(n_clusters=n_clusters)
    labels = model.fit_predict(X)
    # Create scatter plot of data points colored by cluster label
    plt.scatter(X, betas['Stock_Name'], c=labels, cmap='rainbow')
    plt.xlabel('Beta')
    plt.ylabel('Beta')
    plt.title(f"n_clusters={n_clusters}")
    cluster_counts = np.bincount(labels)
    for i in range(n_clusters):
        print(f"Cluster {i+1} has {cluster_counts[i]} observations")
    plt.yticks([])
    plt.show()

Ето резултата:

Визуализацията може да е субективна и различните хора ще възприемат данните по различен начин.

Струва ми се обаче, че диаграмата на разсейване с n_clusters=4 има най-отчетливите и ясни клъстери, особено тези между бета от 0,8 и 1,3. Външните клъстери, Клъстер 1 (океански сини точки) и Клъстер 4 (червени точки), могат да се разглеждат като възможни извънредни стойности, където акциите имат по-ниска или по-висока волатилност - което може да помогне за диверсификация на портфейла, в зависимост от толерантността на индивида към риска. Клъстер 1 може да минимизира променливостта (поради по-ниска бета версия), но клъстер 4 може да увеличи промените в портфолиото.

Въз основа на наблюденията по-горе, ние ще изберем n клъстери=4 като оптимален брой клъстери за групиране на бета версии на акции. Нека присвоим подходящ клъстер за всяка акция.

optimal_n_clusters = 4
agglom = AgglomerativeClustering(n_clusters=optimal_n_clusters)
cluster_labels = agglom.fit_predict(X)
betas['Cluster'] = cluster_labels
betas

Ето представянето на визуализацията, като всеки клъстер е обозначен.

 cluster4 = sns.lmplot(data=betas, x='Cluster', y='Beta', hue='Cluster', 
                    legend=True, legend_out=True);

И ето диаграмата на цигулката, която показва разпределението на всеки клъстер.

sns.violinplot(x='Cluster', y='Beta', data=betas)
plt.show()

Заключение

Можем да използваме горепосочената информация, за да създадем примерен портфейл от акции по различни начини. Без добавяне на нови данни, един метод е произволно да се избере акция от всеки клъстер и да се изчисли средна бета, за да се измери общата променливост на портфейла от акции, за да се види дали определена бета цифра е приемлива от гледна точка на риска.

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

Това проучване може да бъде разбито за по-нататъшно проучване и n_cluster=4 не е непременно най-добрият вариант за създаване на портфейл от акции. Освен това, много други фактори могат да бъдат взети предвид при определяне на подходящия брой клъстери. За да сравним клъстерите, можем да използваме резултати, различни от резултата на силуета, като индекса на Calinski-Harabasz. Вярвам обаче, че това беше добро въведение към тема, която може да бъде проучена допълнително.

И накрая, всичко се свежда до факторите, които са от решаващо значение за инвеститора при изграждането на портфолио.

Благодаря ви, че прочетохте. Ако има нещо неясно, моля да ме уведомите. Ще се радвам да отговоря на вашите въпроси.

Приложение

Бета функция

#Betas Function 

#initiate the function
def betas(markets, stocks, start_date, end_date):
#download the historical data for the index/market
  market = yf.download(markets, start_date, end_date)
  market['stock_name'] = markets
#calculate daily returns 
  market['daily_return'] = market['Close'].pct_change(1)
#calculate standard deviation of the returns
  market_std = market['daily_return'].std()
  market.dropna(inplace=True)
  market = market[['Close', 'stock_name', 'daily_return']] 
#download the historical data for each stock and calculate its standard deviation 
#using for loops/iteration 
  frames = []
  stds = []
  for i in stocks: 
    data = yf.download(i, start_date, end_date)
    data['stock_name'] = i
    data['daily_return'] = data['Close'].pct_change(1)
    data.dropna(inplace=True)
    data = data[[ 'Close', 'stock_name', 'daily_return']]
    data_std = data['daily_return'].std()
    frames.append(data)
    stds.append(data_std)
#for each stock calculate its correlation with index/market 
  stock_correlation = []
  for i in frames: 
    correlation = i['daily_return'].corr(market['daily_return'])
    stock_correlation.append(correlation)
#calculate beta 
  betas = []
  for b,i in zip(stock_correlation, stds):
    beta_calc = b * (i/market_std)
    betas.append(beta_calc)
#form dataframe with the results 
  dictionary = {stocks[e]: betas[e] for e in range(len(stocks))}
  dataframe = pd.DataFrame([dictionary]).T
  dataframe.reset_index(inplace=True)
  dataframe.rename(
    columns={"index": "Stock_Name", 0: "Beta"},
    inplace=True,)
  return dataframe

Ако искате да получавате актуална информация за борсата три пъти седмично, моля, абонирайте се за https://szymanskiresearch.substack.com/ . Безплатно е!

Свързана публикация:



Отказ от отговорност: Информацията, съдържаща се в тази публикация в блога, е само за образователни и развлекателни цели. Това също не е инвестиционен съвет. Всички възгледи, изразени в този блог, са мои собствени и не представляват мненията на каквото и да е образувание, с което съм бил, сега съм или ще бъда свързан.

Абонирайте се за DDIntel Тук.

Посетете нашия уебсайт тук: https://www.datadriveninvestor.com

Присъединете се към нашата мрежа тук: https://datadriveninvestor.com/collaborate