В этой статье вы узнаете, как работает неконтролируемое машинное обучение и как находить пары, используя биржевые данные для парной торговли.
Здесь мы будем использовать три типа методов кластерного анализа, т. е.
i) Кластеризация K-средних.
ii) Иерархический кластер
iii) Кластеризация распространения сходства

Загрузите необходимый набор данных:
СКАЧАТЬ НАБОР ДАННЫХ ЗДЕСЬ
Этот набор данных содержит список 1000 лучших компаний на основе рыночной капитализации индийских компаний на март 2020 года. Нам нужен этот набор данных

Индийские биржевые данные неудобны. Поэтому необходимо иметь список компаний и перебирать их для получения данных.

  1. Импортировать необходимые модули:
from nsepy import get_history
from datetime import date
import pandas as pd
import numpy as np
import seaborn as sns
from sklearn.preprocessing import StandardScaler

2. Чтение файла excel для получения списка компаний:

df = pd.read_excel("MCAP_31032020_TOP1000.xlsx")
stock_1000 = list(df.Symbol)

3. Получение списка 500 лучших компаний:

stock = stock_1000[:500]

4. Проходим по списку и получаем наши данные по каждой из компаний:

the_stock_data = {}
for symbols in stock:
    try:
        the_stock_data[symbols] = get_history(symbol = symbols , start = date(2019, 1, 1), end = date(2022, 1,31))
   except:
       continue

Здесь мы указываем дату начала и дату окончания.

5. Объединение полученных данных:

data = pd.concat(the_stock_data)

6. Сброс индекса:

data = data.reset_index()

7. Извлечение необходимых данных, т. е. даты и цены закрытия.

data = data.pivot(index='Date', columns='Symbol', values = 'Close')
data.head()

8. Описываем метод и ставим десятичную точку на 3:

pd.set_option('precision', 3)
data.describe().T.head(10)

9. Обработка нулевых значений:

Проверка существования нулевых значений:

data.isnull().values.any()

Визуализация нулевых значений с использованием отсутствующих:

import missingno
missingno.matrix(data)

Удаление столбцов с нулевыми значениями более 20% данных:

print('Data Shape before cleaning =', data.shape)
missing_percentage = data.isnull().mean().sort_values(ascending=False)
dropped_list = sorted(list(missing_percentage[missing_percentage > 0.2].index))
data.drop(labels=dropped_list, axis=1, inplace=True)
print('Data Shape after cleaning =', data.shape)

Заполнение нулевых значений с использованием методов ffill и bfill:

data = data.fillna(method='ffill')
data = data.fillna(method='bfill')

Теперь наш набор данных не содержит нулевых значений.

12. Хранение данных для будущего использования:

data.to_csv('NSE500_stock_data')

13. Расчет доходности и волатильности и создание фрейма данных:

#Calculate returns and create a data frame
returns         = data.pct_change().mean()*266
returns         = pd.DataFrame(returns)
returns.columns = ['returns']
#Calculate the volatility
returns['volatility'] = data.pct_change().std()*np.sqrt(266)
data                  = returns
data.head()

14. Визуализация данных (доходность и волатильность):

sns.displot(data, x="returns", y="volatility")

15. Использование StandardScalar для преобразования данных:

#Prepare the scaler
scale = StandardScaler().fit(data)
#Fit the scaler
scaled_data = pd.DataFrame(scale.fit_transform(data),columns = data.columns, index = data.index)
X = scaled_data
X.head()

16. Визуализация данных после использования Standard Scalar:

sns.displot(X, x="returns", y="volatility")

17. Выполнение кластеризации K-средних:

Нахождение количества кластеров

i) Метод локтя:

from sklearn.cluster import KMeans
from sklearn import metrics
import matplotlib.pyplot as plt
%matplotlib inline
K = range(1,15)
distortions = []
#Fit the method
for k in K:
    kmeans = KMeans(n_clusters = k)
    kmeans.fit(X)
    distortions.append(kmeans.inertia_)
#Plot the results
fig = plt.figure(figsize= (15,5))
plt.plot(K, distortions, 'bx-')
plt.xlabel('Values of K')
plt.ylabel('Distortion')
plt.title('Elbow Method')
plt.grid(True)
plt.show()

Использование библиотеки Kned, которая находит оптимальное количество кластеров:

from kneed import KneeLocator
kl = KneeLocator(K, distortions, curve="convex", direction="decreasing")
kl.elbow

ii) Метод силуэта:

from sklearn.metrics import silhouette_score
#For the silhouette method k needs to start from 2
K = range(2,15)
silhouettes = []
#Fit the method
for k in K:
    kmeans = KMeans(n_clusters=k, random_state=42, n_init=10, init='random')
    kmeans.fit(X)
    silhouettes.append(silhouette_score(X, kmeans.labels_))
#Plot the results
fig = plt.figure(figsize= (15,5))
plt.plot(K, silhouettes, 'bx-')
plt.xlabel('Values of K')
plt.ylabel('Silhouette score')
plt.title('Silhouette Method')
plt.grid(True)
plt.show()

Использование библиотеки Kned, которая находит оптимальное количество кластеров:

kl = KneeLocator(K, silhouettes, curve="convex", direction="decreasing")
print('Suggested number of clusters: ', kl.elbow)

Построение нашего алгоритма k-Means с 4 кластерами:

c = 4
#Fit the model
k_means = KMeans(n_clusters=c)
k_means.fit(X)
prediction = k_means.predict(X)
#Plot the results
centroids = k_means.cluster_centers_
fig = plt.figure(figsize = (18,10))
ax = fig.add_subplot(111)
scatter = ax.scatter(X.iloc[:,0],X.iloc[:,1], c=k_means.labels_, cmap="rainbow", label = X.index)
ax.set_title('k-Means Cluster Analysis Results')
ax.set_xlabel('Mean Return')
ax.set_ylabel('Volatility')
plt.colorbar(scatter)
plt.plot(centroids[:,0],centroids[:,1],'sg',markersize=10)
plt.show()

Нахождение количества экземпляров в каждом кластере:

clustered_series = pd.Series(index=X.index, data=k_means.labels_.flatten())
clustered_series_all = pd.Series(index=X.index, data=k_means.labels_.flatten())
clustered_series = clustered_series[clustered_series != -1]
plt.figure(figsize=(12,8))
plt.barh(range(len(clustered_series.value_counts())),clustered_series.value_counts())
plt.title('Clusters')
plt.xlabel('Stocks per Cluster')
plt.ylabel('Cluster Number')
plt.show()

18. Выполнение иерархической кластеризации:

#x-axis - stock, y-axis - distance between them
from sklearn.cluster import AgglomerativeClustering
import scipy.cluster.hierarchy as shc
plt.figure(figsize=(15, 10))
plt.title("Dendrograms")
dend = shc.dendrogram(shc.linkage(X, method='ward'))

Находим количество кластеров:

plt.figure(figsize=(15, 10))
plt.title("Dendrogram")
dend = shc.dendrogram(shc.linkage(X, method='ward'))
plt.axhline(y=9.5, color='purple', linestyle='--')

Построение нашего алгоритма иерархической кластеризации с 5 кластерами:

#Fit the model
clusters = 5
hc = AgglomerativeClustering(n_clusters= clusters, affinity='euclidean', linkage='ward')
labels = hc.fit_predict(X)
#Plot the results
fig = plt.figure(figsize=(15,10))
ax = fig.add_subplot(111)
scatter = ax.scatter(X.iloc[:,0], X.iloc[:,1], c=labels, cmap='rainbow')
ax.set_title('Hierarchical Clustering Results')
ax.set_xlabel('Mean Return')
ax.set_ylabel('Volatility')
plt.colorbar(scatter)
plt.show()

19. Выполнение кластеризации распространения сходства:

Построение нашего алгоритма кластеризации Affinity Propagation:

from sklearn.cluster import AffinityPropagation
#Fit the model
ap = AffinityPropagation()
ap.fit(X)
labels1 = ap.predict(X)
#Plot the results
fig = plt.figure(figsize=(15,10))
ax = fig.add_subplot(111)
scatter = ax.scatter(X.iloc[:,0], X.iloc[:,1], c=labels1, cmap='rainbow')
ax.set_title('Affinity Propagation Clustering Results')
ax.set_xlabel('Mean Return')
ax.set_ylabel('Volatility')
plt.colorbar(scatter)
plt.show()

Получение количества кластеров и их расположение для лучшего вида:

from itertools import cycle
#Extract the cluster centers and labels
cci = ap.cluster_centers_indices_
labels2 = ap.labels_
#Print their number
clusters = len(cci)
print('The number of clusters is:',clusters)
#Plot the results
X_ap = np.asarray(X)
plt.close('all')
plt.figure(1)
plt.clf
fig=plt.figure(figsize=(15,10))
colors = cycle('cmykrgbcmykrgbcmykrgbcmykrgb')
for k, col in zip(range(clusters),colors):
    cluster_members = labels2 == k
    cluster_center = X_ap[cci[k]]
    plt.plot(X_ap[cluster_members, 0], X_ap[cluster_members, 1], col + '.')
    plt.plot(cluster_center[0], cluster_center[1], 'o', markerfacecolor=col, markeredgecolor='k', markersize=12)
    for x in X_ap[cluster_members]:
        plt.plot([cluster_center[0], x[0]], [cluster_center[1], x[1]], col)
plt.show()

20. Использование оценки силуэта, чтобы определить, какой метод работает лучше всего:

print("k-Means Clustering", metrics.silhouette_score(X, k_means.labels_, metric='euclidean'))
print("Hierarchical Clustering", metrics.silhouette_score(X, hc.fit_predict(X), metric='euclidean'))
print("Affinity Propagation Clustering", metrics.silhouette_score(X, ap.labels_, metric='euclidean'))

Здесь алгоритм K-средних показал себя хорошо.

21. Извлекать торговые пары

i) Общее количество кластеров и общее количество пар находится:

cluster_size_limit = 1000
counts             = clustered_series.value_counts()
symbol_count     = counts[(counts>1) & (counts<=cluster_size_limit)]
print ("Number of clusters: %d" % len(symbol_count))
print ("Number of Pairs: %d" % (symbol_count*(symbol_count-1)).sum())

ii) Чтение данных, которые мы сохранили ранее:

data1 = pd.read_csv("NSE500_stock_data")

ii) Поиск уникальных пар:

def find_cointegrated_pairs(data, significance=0.05):
    n = data.shape[1]
    score_matrix = np.zeros((n, n))
    pvalue_matrix = np.ones((n, n))
    keys = data.keys()
    pairs = []
    for i in range(1):
        for j in range(i+1, n):
            S1 = data[keys[i]]
            S2 = data[keys[j]]
            result = coint(S1, S2)
            score = result[0]
            pvalue = result[1]
            score_matrix[i, j] = score
            pvalue_matrix[i, j] = pvalue
            if pvalue < significance:
                pairs.append((keys[i], keys[j]))
    return score_matrix, pvalue_matrix, pairs
from statsmodels.tsa.stattools import coint
cluster_dict = {}
for i, clust in enumerate(symbol_count.index):
    symbols = clustered_series[clustered_series == clust].index
    score_matrix, pvalue_matrix, pairs = find_cointegrated_pairs(data1[symbols])
    cluster_dict[clust] = {}
    cluster_dict[clust]['score_matrix'] = score_matrix
    cluster_dict[clust]['pvalue_matrix'] = pvalue_matrix
    cluster_dict[clust]['pairs'] = pairs
pairs = []
for cluster in cluster_dict.keys():
    pairs.extend(cluster_dict[cluster]['pairs'])
print ("Number of pairs:", len(pairs))
print ("In those pairs, we found %d unique symbols." % len(np.unique(pairs)))
print(pairs)

22. Визуализируйте торговые пары с помощью TSNE (встраивание стохастического соседа с t-распределением):

from sklearn.manifold import TSNE
import matplotlib.cm as cm
stocks_data = np.unique(pairs)
X_data = pd.DataFrame(index=X.index, data=X).T
in_pairs_series = clustered_series.loc[stocks_data]
stocks = list(np.unique(pairs))
X_pairs = X_data.T.loc[stocks]
X_pairs.head()
X_tsne = TSNE(learning_rate=30, perplexity=5, random_state=42, n_jobs=-1).fit_transform(X_pairs)
X_tsne
plt.figure(1, facecolor='white',figsize=(15,10))
plt.clf()
plt.axis('off')
for pair in pairs:
    ticker1 = pair[0]
    loc1 = X_pairs.index.get_loc(pair[0])
    x1, y1 = X_tsne[loc1, :]
    ticker2 = pair[0]
    loc2 = X_pairs.index.get_loc(pair[1])
    x2, y2 = X_tsne[loc2, :]
    plt.plot([x1, x2], [y1, y2], 'k-', alpha=0.3, c='b');
plt.scatter(X_tsne[:, 0], X_tsne[:, 1], s=215, alpha=0.8, c=in_pairs_series.values, cmap=cm.Paired)
plt.title('TSNE Visualization of Pairs');
# Join pairs by x and y
for x,y,name in zip(X_tsne[:,0],X_tsne[:,1],X_pairs.index):
    label = name
    plt.annotate(label,
                 (x,y),
                 textcoords="offset points",
                 xytext=(0,10),
                 ha='center')
plt.show()

Результат визуализации:

Надеюсь, вам понравилось изучение неконтролируемого машинного обучения!
Продолжайте изучать!