В этой статье будет показано, как использование модели K-ближайших соседей в стратегии возврата к среднему может улучшить ее производительность. Кроме того, это будет проверено на истории, чтобы обеспечить показатель производительности. Это часть 5 из 5 в серии Алгоритмы машинного обучения на рынках. В конце этой серии методы машинного обучения будут сравниваться друг с другом.

Введение
Основная идея алгоритма k-NN заключается в том, что подобные вещи находятся рядом друг с другом. Когда требуется прогноз для невидимой точки данных, алгоритм просматривает «k» ближайших точек данных или «k» ближайших соседей в обучающем наборе и присваивает значение или класс на основе этих соседей.

Он работает с помощью двух известных методов:

  1. Классификация. Для заданной точки данных, которую необходимо классифицировать, алгоритм идентифицирует k ближайших соседей из обучающей выборки. Затем точка данных назначается классу, который имеет наибольшее количество представителей среди этих «k» соседей. Например, если k = 5 и 3 из 5 ближайших соседей относятся к классу A, а 2 — к классу B, точка данных будет классифицирована как класс A.
  2. Регрессия. Для задач регрессии алгоритм вычисляет среднее значение (или иногда медиану) для k ближайших соседей.

Расстояние между точками данных можно рассчитать различными способами, но наиболее распространенным методом является евклидово расстояние. Другие методы включают манхэттенское расстояние, расстояние Минковского и т. д.
Ключевым параметром алгоритма k-NN является значение «k». Если «k» слишком мало, модель может быть чрезмерно чувствительна к шуму в данных. Если «k» слишком велико, модель может включать точки, которые находятся слишком далеко и, следовательно, менее релевантны. Выбор правильного «k» часто требует проб и ошибок или таких методов, как перекрестная проверка.

Алгоритм k in k-Nearest Neighbours (k-NN) — это гиперпараметр, который вы выбираете, и он представляет количество ближайших соседей, которые следует учитывать при прогнозировании. k может принимать любое целочисленное значение, от 1 до общего количества наблюдений в вашем обучающем наборе.

Именно по той причине, что построена kNN, это не алгоритм «прогнозирования», поскольку он берет точку данных, которая уже возникла, а затем классифицирует ее. Поэтому в этом случае я решил, что kNN будет использоваться для определения того, находятся ли данные (цена) в нисходящем или восходящем тренде, а затем соответственно разместить сделку. Теоретически это будет работать следующим образом:
Для заданного интервала для прогнозирования цены акции найдите k интервалов в обучающем наборе, которые имеют наиболее схожие значения характеристик с заданным интервалом. Затем возьмите среднее (для регрессии) направление акций на этих интервалах «k» в качестве прогнозируемого движения для интервала прогнозирования.

import yfinance as yf
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score, confusion_matrix
from sklearn.preprocessing import StandardScaler
import numpy as np

# Download the data
tesla = yf.download('TSLA', start='2023-04-14', end='2023-04-28', interval='15m')

# Create labels: 1 if the price increased in the next interval, 0 if it decreased or stayed the same
tesla['Direction'] = np.where(tesla['Close'].shift(-1) > tesla['Close'], 1, 0)

# Define the features and target
features = tesla.drop(['Direction', 'Close'], axis=1)  # removing Close as it directly influences the label
target = tesla['Direction']

# Normalize the features
scaler = StandardScaler()
features = scaler.fit_transform(features)

# Split the data into a training set and a test set
features_train, features_test, target_train, target_test = train_test_split(features[:-1], target[:-1], test_size=0.2, random_state=42)

# Test different k values
for k in range(1, 20):
    model = KNeighborsClassifier(n_neighbors=k)
    model.fit(features_train, target_train)
    predictions = model.predict(features_test)
    accuracy = accuracy_score(target_test, predictions)
    print(f"For k = {k}, accuracy = {accuracy}")

Для k = 1 точность = 0,5961538461538461
Для k = 2 точность = 0,5
Для k = 3 точность = 0,4230769230769231
Для k = 4 , точность = 0,4807692307692308
Для k = 5, точность = 0,46153846153846156
Для k = 6, точность = 0,5192307692307693
Для k = 7, точность = 0,5384615384615384
Для k = 8, точность = 0,5961538461538461
Для k = 9, точность = 0,5961538461538461
Для k = 10, точность = 0,5769230769230769
Для k = 11, точность = 0,519230769230769 3
Для k = 12, точность = 0,5192307692307693
Для k = 13, точность = 0,5384615384615384
Для k = 14, точность = 0,5961538461538461
Для k = 15, точность = 0,53846153 84615384
Для k = 16 точность = 0,5576923076923077
Для k = 17 точность = 0,5
Для k = 18 точность = 0,5192307692307693
Для k = 19 точность = 0,5

Этот сценарий сначала загружает данные из Yahoo Finance. Затем он создает метку, которая указывает, пошла ли цена вверх или вниз в следующем интервале. Он нормализует функции, случайным образом разбивает данные на обучающий набор и тестовый набор, а затем проверяет различные значения «k», чтобы определить, какое из них дает наибольшую точность на тестовый набор.

import matplotlib.pyplot as plt

# Choose the best 'k' based on previous step
best_k = 1

# Retrain the model using the best 'k'
model = KNeighborsClassifier(n_neighbors=best_k)
model.fit(features_train, target_train)

# Predict the direction for all data points
tesla['Predicted Direction'] = model.predict(features)

# Calculate returns from the strategy
tesla['Return'] = np.where(tesla['Predicted Direction'].shift() == 1, tesla['Close'].pct_change(), 0)

# Calculate cumulative returns
tesla['Cumulative Return'] = (1 + tesla['Return']).cumprod()

# Plot cumulative returns
plt.plot(tesla['Cumulative Return'])
plt.title('Cumulative Returns from k-NN Strategy')
plt.xticks(rotation=90)
plt.show()

Торговая стратегия включает в себя «покупку» акции, когда предсказанное направление идет вверх, и «продажу», когда предсказанное направление идет вниз. Сюжет представляет собой совокупную доходность.

Этот сюжет дает ощущение «слишком хорошо, чтобы быть правдой». Это часто является результатом переобучения модели, и в этом случае это верно для рандомизации наборов обучающих и тестовых данных, а также когда «k» очень мало, поскольку модель может быть чрезмерно чувствительна к шуму в данных. Это может закончиться «запоминанием» обучающих данных, а не обучением их обобщению.

Для графика k=9 результаты также относительно многообещающие и показывают признаки того, что модель подходит. Однако есть некоторые проблемы с конструкцией модели, которые я сейчас рассмотрю.

Вместо случайного разделения данных обучающие и тестовые наборы теперь будут создаваться с использованием жесткой точки разделения (80/20 — см. ниже). Рандомизация данных позволяет обучающим и тестовым наборам быть репрезентативными для любого общего распределения данных. Но в случае финансовых временных рядов часто имеет смысл сохранять временной порядок данных (это связано с тем, что цель часто состоит в том, чтобы предсказать будущие цены на основе прошлых цен, поэтому нет смысла тренироваться на них). данные из будущего).

# Calculate the split point
split_point = int(len(features) * 0.8)

Для k = 1 точность = 0,6153846153846154
Для k = 2 точность = 0,5384615384615384
Для k = 3 точность = 0,5192307692307693
Для k = 4 , точность = 0,4807692307692308
Для k = 5 точность = 0,5769230769230769
Для k = 6 точность = 0,5384615384615384
Для k = 7 точность = 0,576923076923 0769
Для k = 8 точность = 0,5192307692307693
Для k = 9 точность = 0,5192307692307693
Для k = 10 точность = 0,46153846153846156
Для k = 11, точность = 0,5769230769230769
Для k = 12, точность = 0,5576923076923077
Для k = 13, точность = 0,5769230769230769
Для k = 14, точность = 0,5576923076923077
Для k = 15 точность = 0,5769230769230769
Для k = 16 точность = 0,5769230769230769
Для k = 17 точность = 0,5769230769230769
> Для к = 18, точность = 0,5961538461538461
Для k = 19 точность = 0,5769230769230769

Даже при жестком разделении данных для обучающего и тестового наборов отдача от этой стратегии также весьма многообещающая; с ограниченным снижением прогноза.

Добавление kNN в стратегию возврата к среднему

import yfinance as yf
import pandas as pd
import numpy as np
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler

# Download the data
tesla = yf.download('TSLA', start='2023-04-14', end='2023-04-28', interval='15m')

# Define the moving average window
window = 15

# Calculate the moving average
tesla['Moving Average'] = tesla['Close'].rolling(window=window).mean()

# Create labels: 1 if the price is below the moving average (expecting it to rise towards the mean), 
# 0 if it is above (expecting it to fall towards the mean)
tesla['Below Moving Average'] = np.where(tesla['Close'] < tesla['Moving Average'], 1, 0)

# Define the features and target
features = tesla.drop(['Below Moving Average', 'Close', 'Moving Average'], axis=1)
target = tesla['Below Moving Average']

# Normalize the features
scaler = StandardScaler()
features = scaler.fit_transform(features)

# Calculate the split point
split_point = int(len(features) * 0.8)

# Split the data into a training set and a test set
features_train = features[:split_point]
features_test = features[split_point:]
target_train = target[:split_point]
target_test = target[split_point:]

# Test different k values
for k in range(1, 20):
    model = KNeighborsClassifier(n_neighbors=k)
    model.fit(features_train, target_train)
    predictions = model.predict(features_test)
    accuracy = accuracy_score(target_test, predictions)
    print(f"For k = {k}, accuracy = {accuracy}")

В этом скрипте рассчитывается скользящая средняя цены закрытия за последние 15 интервалов. Затем определяется новая целевая переменная «Ниже скользящей средней»; что равно 1, если текущая цена ниже скользящей средней, и 0, если она выше.
Характеристики нормализуются, данные разбиваются на обучающие и тестовые наборы, а классификатор k-ближайших соседей тестируется на обучающих данных для разных значения к. Для каждого k мы вычисляем точность модели на тестовых данных.

Для k = 1 точность = 0,5192307692307693
Для k = 2 точность = 0,5192307692307693

Для k = 3 точность = 0,4807692307692308
Для k = 4 , точность = 0,4807692307692308
Для k = 5 точность = 0,46153846153846156
Для k = 6 точность = 0,5192307692307693
Для k = 7 точность = 0,46153846153846 156
Для k = 8 точность = 0,46153846153846156
Для k = 9 точность = 0,46153846153846156
Для k = 10 точность = 0,46153846153846156
Для k = 11 точность = 0,461538461538 46156
Для k = 12, точность = 0,46153846153846156
Для k = 13, точность = 0,46153846153846156
Для k = 14, точность = 0,46153846153846156
Для k = 15, точность = 0,461538461538 46156
Для к = 16, точность = 0,46153846153846156
Для k = 17, точность = 0,46153846153846156
Для k = 18, точность = 0,46153846153846156
Для k = 19, точность = 0,46153846153846 156

import matplotlib.pyplot as plt

# Test different k values and choose the one with the highest accuracy
accuracies = []
for k in range(1, 20):
    model = KNeighborsClassifier(n_neighbors=k)
    model.fit(features_train, target_train)
    predictions = model.predict(features_test)
    accuracy = accuracy_score(target_test, predictions)
    accuracies.append(accuracy)
best_k = accuracies.index(max(accuracies)) + 1

# Train the model with the best k
model = KNeighborsClassifier(n_neighbors=best_k)
model.fit(features_train, target_train)

# Predict on the test set
predictions = model.predict(features_test)

# Create a DataFrame for calculating returns
test_data = tesla.iloc[split_point:]
test_data['Predictions'] = predictions

# Calculate returns
test_data['Returns'] = test_data['Close'].pct_change()
test_data['Strategy Returns'] = test_data['Returns'] * test_data['Predictions'].shift()

# Calculate cumulative returns
test_data['Cumulative Market Returns'] = (test_data['Returns'] + 1).cumprod()
test_data['Cumulative Strategy Returns'] = (test_data['Strategy Returns'] + 1).cumprod()

# Plot the cumulative returns
plt.figure(figsize=(10,5))
plt.plot(test_data['Cumulative Market Returns'], color='blue', label='Market Returns')
plt.plot(test_data['Cumulative Strategy Returns'], color='green', label='Strategy Returns')
plt.title('Cumulative Returns')
plt.legend()
plt.show()

График показывает, что когда эта конкретная модель применяется к стратегии возврата к среднему, она не работает особенно хорошо. При этом существует большой флетовый период, когда рынок закрыт, и поэтому весь потенциал этой стратегии не виден. Более крупный набор данных действительно лучше подходит для определения эффективности этой стратегии.

Дополнительные ограничения

Как следует из максимального k, в этой модели всего 19 точек обучающих данных. Это, вероятно, ограничит воспроизводимость модели и изменит ее производительность для разных периодов.
Обратите внимание, что это очень простая модель и может не подходить для реальной торговли. В частности, не учитываются транзакционные издержки, влияние решений модели на рынок или риск, связанный с различными решениями. Он также не имеет механизма для обработки отсутствующих данных или выбросов. Наконец, он использует очень простой набор функций и не включает никакой информации о более широком рынке или других потенциально важных факторах.
В коде используется стандартный скалер, учитывающий весь набор данных, прежде чем он будет разделен на обучающую и тестовую части. наборы. Это означает, что тестовый набор изменяется всем набором данных и поэтому может рассматриваться как второстепенный источник переобучения.