Алгоритмическая стратегия vs/+


Благодарности соответствующему владельцу, Получил его от Goolge Search — Relatant to KNN Means topic

Часть 1. K-Nearest Neighbors (KNN)

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

Введение

С возвращением, коллеги трейдеры и энтузиасты! В предыдущей экспедиции по царству алгоритмической торговли мы исследовали захватывающий ландшафт от создания стратегий до гипероптимизации, став свидетелями ошеломляющей прибыли при тестировании на истории. А теперь пристегните ремни, чтобы перейти к следующему этапу нашего путешествия, где мы погружаемся в запутанный мир K-Nearest Neighbors (KNN). Сегодня я расскажу о том, как KNN послужил нашим компасом, разделив 138 криптовалютных фьючерсов на Binance на пять различных категорий в зависимости от волатильности. Приготовьтесь стать свидетелем волшебства и узнать, как эта стратегическая классификация готовит почву для еще больших приключений со скрытыми марковскими моделями (HMM) и не только.

Основы машинного обучения: раскрываем основы алгоритмического мастерства

Добро пожаловать, начинающие трейдеры и пытливые умы! Прежде чем мы углубимся в замысловатый танец K-Nearest Neighbors (KNN) и его преобразующее влияние на нашу торговую стратегию, давайте уделим немного времени, чтобы осветить фундаментальные концепции, лежащие в основе царства машинного обучения.

1. Контролируемое обучение:

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

2. Обучение без учителя:

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

Отправляясь в путешествие по основам машинного обучения, помните об этих основополагающих концепциях. Они служат фундаментом, на котором мы строим наше понимание того, как K-Nearest Neighbors (KNN) будет гениально способствовать эволюции нашей торговой стратегии. Так что пристегните ремни, ведь приключение только началось!

Типы концепций машинного обучения

Раскрываем магию KNN:

KNN — это не просто алгоритм; Это стратегический компаньон, который ведет нас через лабиринт динамики рынка. В этой статье мы рассмотрим, как KNN становится компасом, который ведет нас через огромное море криптовалютных активов, особенно на платформе Binance.

Суть кластеризации:

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

От 138 до 5: Категоризация криптофьючерсов:

На этом этапе нашего путешествия я раскрою тонкости того, как KNN помог нам разделить 138 криптовалютных фьючерсов на Binance на пять отдельных категорий в зависимости от волатильности. Эта стратегическая категоризация закладывает основу для более совершенных и целенаправленных торговых подходов, закладывая основу для того, что ждет нас впереди в нашем исследовании скрытых марковских моделей (HMM) и за их пределами.

Итак, затяните ремни безопасности, когда мы отправляемся в это увлекательное путешествие, в котором KNN преобразует данные в полезную информацию, продвигая нашу торговую стратегию на новые высоты. Да начнется волшебство кластеризации!

Объяснение кода

Шаг 1: Импорт библиотек и удаление предупреждений

# Remove unwanted warnings
import warnings
warnings.simplefilter(action=’ignore’, category=FutureWarning)

Этот фрагмент кода игнорирует будущие предупреждения о более чистом выводе.

Шаг 2: Импорт необходимых библиотек для управления данными

# Data extraction and management
import pandas as pd
import numpy as np
from pandas_datareader.data import DataReader
from pandas_datareader.nasdaq_trader import get_nasdaq_symbols

Здесь Pandas импортируется для манипулирования данными, а DataReader используется для получения финансовых данных.

Шаг 3: Импорт библиотек для конструирования признаков и машинного обучения

# Feature Engineering
from sklearn.preprocessing import StandardScaler
# Machine Learning
from sklearn.cluster import KMeans
from sklearn import metrics
from kneed import KneeLocator

Эти библиотеки предназначены для масштабирования функций (StandardScaler) и машинного обучения (кластеризация KMeans).

Шаг 4: Импорт библиотек для коинтеграции и статистики

# Cointegration and Statistics
from statsmodels.tsa.stattools import coint
import statsmodels.api as sm

Эти библиотеки используются для статистического анализа, в частности, для коинтеграционных тестов.

Шаг 5: Импорт библиотек для визуализации отчетов

# Reporting visualization
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
import matplotlib.cm as cm
%matplotlib inline

Эти библиотеки предназначены для визуализации результатов, в том числе t-SNE для уменьшения размерности.

Шаг 6: Импорт библиотеки API криптовалютной биржи (ccxt)

import ccxt

Библиотека ccxt позволяет взаимодействовать с криптовалютными биржами. В этом коде он используется для получения рыночных символов.

Шаг 7: Определение списка криптовалютных символов

symbols = [«BTC/USDT:USDT»,
«GMT/USDT:USDT»,
«ETH/USDT:USDT»,
«MTL/USDT:USDT»,
«NEAR/USDT:USDT»,
«SOL/USDT:USDT»,
«OGN/USDT:USDT»,
«ZIL/USDT:USDT»,
«APE/USDT:USDT»,
«XRP/USDT:USDT»,
«ADA/USDT:USDT»,
«AVAX/USDT:USDT»,
«KNC/USDT:USDT»,
«DOGE/USDT:USDT»,
«WAVES/USDT:USDT»,
«1000SHIB/USDT:USDT»,
«FTM/USDT:USDT»,
«BNB/USDT:USDT»,
«XMR/USDT:USDT»,
«DOT/USDT:USDT»,
«GALA/USDT:USDT»,
«MATIC/USDT:USDT»,
«LRC/USDT:USDT»,
«RUNE/USDT:USDT»,
«AUDIO/USDT:USDT»,
«FIL/USDT:USDT»,
«ETC/USDT:USDT»,
«EOS/USDT:USDT»,
«ZEC/USDT:USDT»,
«AXS/USDT:USDT»,
«LTC/USDT:USDT»,
«SAND/USDT:USDT»,
«LINK/USDT:USDT»,
«SXP/USDT:USDT»,
«ATOM/USDT:USDT»,
«BCH/USDT:USDT»,
«PEOPLE/USDT:USDT»,
«MANA/USDT:USDT»,
«AAVE/USDT:USDT»,
«ALICE/USDT:USDT»,
«BNX/USDT:USDT»,
«KAVA/USDT:USDT»,
«CRV/USDT:USDT»,
«ONE/USDT:USDT»,
«VET/USDT:USDT»,
«THETA/USDT:USDT»,
«DYDX/USDT:USDT»,
«ICP/USDT:USDT»,
«ALGO/USDT:USDT»,
«SUSHI/USDT:USDT»,
«REN/USDT:USDT»,
«COMP/USDT:USDT»,
«XLM/USDT:USDT»,
«CHZ/USDT:USDT»,
«TLM/USDT:USDT»,
«TRX/USDT:USDT»,
«XTZ/USDT:USDT»,
«FTT/USDT:USDT»,
«IMX/USDT:USDT»,
«CELR/USDT:USDT»,
«WOO/USDT:USDT»,

«HNT/USDT:USDT»,
«EGLD/USDT:USDT»,
«ENJ/USDT:USDT»,
«CELO/USDT:USDT»,
«BAT/USDT:USDT»,
«KSM/USDT:USDT»,
«UNI/USDT:USDT»,
«ROSE/USDT:USDT»,
«BAKE/USDT:USDT»,
«RSR/USDT:USDT»,
«IOST/USDT:USDT»,
«GRT/USDT:USDT»,
«DASH/USDT:USDT»,
«ALPHA/USDT:USDT»,
«FLOW/USDT:USDT»,
«OCEAN/USDT:USDT»,
«DENT/USDT:USDT»,
«CHR/USDT:USDT»,
«OMG/USDT:USDT»,
«HOT/USDT:USDT»,
«LINA/USDT:USDT»,
«SRM/USDT:USDT»,
«COTI/USDT:USDT»,
«SKL/USDT:USDT»,
«NEO/USDT:USDT»,
«SNX/USDT:USDT»,
«ICX/USDT:USDT»,
«AR/USDT:USDT»,
«1INCH/USDT:USDT»,
«API3/USDT:USDT»,
«ANKR/USDT:USDT»,
«DUSK/USDT:USDT»,
«REEF/USDT:USDT»,
«BAL/USDT:USDT»,
«BAND/USDT:USDT»,
«ZRX/USDT:USDT»,
«C98/USDT:USDT»,
«QTUM/USDT:USDT»,
«STORJ/USDT:USDT»,
«IOTA/USDT:USDT»,
«ONT/USDT:USDT»,
«MASK/USDT:USDT»,
«GTC/USDT:USDT»,
«HBAR/USDT:USDT»,
«MKR/USDT:USDT»,
«TOMO/USDT:USDT»,
«ENS/USDT:USDT»,
«ZEN/USDT:USDT»,
«SFP/USDT:USDT»,
«CVC/USDT:USDT»,
«IOTX/USDT:USDT»,
«CTK/USDT:USDT»,
«FLM/USDT:USDT»,
«NKN/USDT:USDT»,
«YFI/USDT:USDT»,
«RLC/USDT:USDT»,
«BTS/USDT:USDT»,
«KLAY/USDT:USDT»,
«BEL/USDT:USDT»,
«XEM/USDT:USDT»,
«ANT/USDT:USDT»,
«SC/USDT:USDT»,
«LIT/USDT:USDT»,
«CTSI/USDT:USDT»,
«STMX/USDT:USDT»,
«UNFI/USDT:USDT»,
«RVN/USDT:USDT»,
«1000XEC/USDT:USDT»,
«RAY/USDT:USDT»,
«BLZ/USDT:USDT»,
«ATA/USDT:USDT»,
«ARPA/USDT:USDT»,
«DGB/USDT:USDT»,
«LPT/USDT:USDT»,
«TRB/USDT:USDT»,
«OP/USDT:USDT»,
«GAL/USDT:USDT»]
formatted_symbols = [symbol.replace(«/USDT:USDT», «») for symbol in symbols]

Определен список криптовалютных символов. Список formatted_symbols создается путем удаления ненужных частей из исходных символов.

Я использую те же активы при тестировании на истории на платформе Freqtrade для криптотрейдинга (бэктетинг и реальная торговля/пробный запуск)

freqtrade с прибылью на истории для RSI, MACD, Bollinger Bands Crypto Algorithmic Strategy 2023 с использованием вышеуказанных активов

Ссылка на приведенное выше объяснение стратегии— Ссылка

Шаг 8: Получение данных о рынке криптовалют с Binance

file_name = «./binance_data.csv»
file_name_coint = «./raw_data_coint_pairs.csv»
load_existing = True
load_coint_pairs = True

if not load_existing:

# Initialize ccxt exchange instance for Binance
exchange = ccxt.binance()

# Fetch all market symbols
symbols = exchange.fetch_markets()

# Define the time period you’re interested in
since_timestamp = exchange.parse8601(‘2023-01-01T00:00:00Z’) # Replace with your desired start time

# Set the timeframe (1h in this case)
timeframe = ‘1h’

# Initialize an empty DataFrame to store the OHLCV data
all_data = pd.DataFrame()

# Fetch OHLCV data for each symbol
for symbol in symbols:
if symbol[‘quote’] == ‘USDT’: # Filter only symbols with ‘/USDT:USDT’ at the end
try:
# Fetch OHLCV data
ohlcv = exchange.fetch_ohlcv(symbol[‘symbol’], timeframe, since=since_timestamp)

# Convert the data to a DataFrame
df = pd.DataFrame(ohlcv, columns=[‘timestamp’, ‘open’, ‘high’, ‘low’, ‘close’, ‘volume’])

# Add symbol column
df[‘symbol’] = symbol[‘symbol’]

# Append the data to the overall DataFrame
all_data = all_data.append(df, ignore_index=True)

except ccxt.NetworkError as e:
print(f»Network error while fetching {symbol[‘symbol’]}: {e}»)
except ccxt.ExchangeError as e:
print(f»Exchange error while fetching {symbol[‘symbol’]}: {e}»)
except Exception as e:
print(f»Error while fetching {symbol[‘symbol’]}: {e}»)

# Save the data to a .csv or .json file
output_file = ‘./binance_data.csv’ # Replace with your desired output file name
all_data.to_csv(output_file, index=False) # Use to_json() for .json format

print(f»Data saved to {output_file}»)

Код извлекает исторические рыночные данные по указанным символам с биржи Binance.

Шаг 9: Чтение и форматирование рыночных данных криптовалюты и предварительная обработка рыночных данных криптовалюты

# Specify the path to your saved .csv file
csv_file_path = file_name # Replace with the actual file path

# Read the .csv file into a DataFrame
df = pd.read_csv(csv_file_path)

# Rename columns for better readability
df.rename(columns={‘timestamp’: ‘Date’, ‘open’: ‘Open’, ‘high’: ‘High’, ‘low’: ‘Low’, ‘close’: ‘Adj Close’, ‘volume’: ‘Volume’, ‘symbol’: ‘Symbol’}, inplace=True)

# Convert timestamps to datetime objects
df[‘Date’] = pd.to_datetime(df[‘Date’] / 1000, unit=’s’)

# Handle duplicate entries by aggregating ‘Adj Close’ values
df_agg = df.groupby([‘Date’, ‘Symbol’]).agg({‘Adj Close’: ‘mean’}).reset_index()

# Pivot the DataFrame to shift rows to columns
df_pivoted = df_agg.pivot(index=’Date’, columns=’Symbol’, values=’Adj Close’)

df_pivoted = df_pivoted.apply(lambda col: col.fillna(col.mean()))

# Display the pivoted DataFrame
pd.set_option(«display.max_columns», None) # Show all columns
pd.set_option(«display.width», 1000) # Adjust display width
pd.set_option(«display.precision», 2) # Set precision

# Filter columns with only «/USDT:USDT» in the suffix
filtered_columns = [col for col in df_pivoted.columns if col.endswith(‘/USDT:USDT’)]

# Create a DataFrame with the filtered columns
filtered_df = df_pivoted[filtered_columns]

# Remove «/USDT:USDT» from column names
filtered_df.columns = [col.replace(‘/USDT:USDT’, ») for col in filtered_df.columns]

print(«filtered_columns Data:»)
filtered_df = filtered_df[formatted_symbols]

filtered_df

Код считывает рыночные данные из CSV-файла, переименовывает столбцы и корректирует формат данных. Код сводит данные так, чтобы символы были в виде столбцов, а даты — в качестве индексов.

Отфильтрованные криптоданные, которые содержат все значения «Adj Close» всех символов, которые мы хотим сгруппировать дальше

Шаг 10: Выполнение масштабирования функций

# Create DataFrame with Returns and Volatility information
df_returns = pd.DataFrame(data.pct_change().mean() * 365, columns=[«Returns»])
# df_returns = pd.DataFrame(data.pct_change().mean() * 255, columns=[«Returns»])
# df_returns[«Volatility»] = data.pct_change().std() * np.sqrt(255) # use for stock trading dayws wihch has 255 aprox trading days
df_returns[«Volatility»] = data.pct_change().std() * np.sqrt(365)
df_returns.head()

# Feature scaling
scaler = StandardScaler()
scaler = scaler.fit_transform(df_returns)
scaled_data = pd.DataFrame(scaler, columns=df_returns.columns, index=df_returns.index)

Код создает фрейм данных отдельно для «Returns» и «Volatility», а затем масштабирует объекты с помощью StandardScaler.

StandardScaler используется для масштабирования объектов в наборе данных перед применением алгоритма кластеризации K-средних. Масштабирование является важным этапом предварительной обработки во многих алгоритмах машинного обучения, включая K-Means, поскольку оно гарантирует, что все объекты имеют одинаковый масштаб. Вот почему это важно в контексте кластеризации K-средних:

Равный вес характеристикам:

  • K-средние используют расстояния между точками данных для отнесения их к кластерам.
  • Если объекты имеют разные масштабы, K-средние могут придать большее значение объектам с более крупными масштабами в процессе кластеризации.
  • Масштабирование гарантирует, что все объекты вносят равный вклад в вычисление расстояния.

Чувствительность к начальным центрам:

  • K-средние чувствительны к первоначальному размещению центров кластеров.
  • Объекты с более крупными масштабами могут доминировать при первоначальном размещении, влияя на сходимость алгоритма.
  • Масштабирование смягчает эту проблему, придавая равный вес всем функциям.

Улучшает сходимость:

  • Масштабирование может помочь K-средним сходиться быстрее и надежнее, так как оно нормализует влияние каждого признака.
  • Это помогает достичь более стабильного и согласованного результата кластеризации в разных прогонах.

StandardScaler подгоняется под возвращаемые данные (df_returns). Метод fit_transform стандартизирует признаки, удаляя среднее значение и масштабируя его до единичной дисперсии. Полученная scaled_data используется на последующих этапах процесса кластеризации.

Таким образом, использование StandardScaler гарантирует, что объекты имеют среднее значение 0 и стандартное отклонение, равное 1, что делает их совместимыми для кластеризации K-средних и способствует справедливому вкладу каждого признака в алгоритм кластеризации.

Шаг 11: Применение кластеризации K-средних

# Find the optimum number of clusters
X = df_scaled.copy()
K = range(1, 15)
distortions = []
for k in K:
kmeans = KMeans(n_clusters=k)
kmeans.fit(X)
distortions.append(kmeans.inertia_)

kl = KneeLocator(K, distortions, curve=»convex», direction=»decreasing»)
c = kl.elbow
print(«Optimum Clusters: «, c)

Инициализация:

  • X = df_scaled.copy(): Создает копию масштабированных данных для кластеризации.

Итерация по возможным номерам кластеров (k):

  • K = range(1, 15) задает диапазон потенциальных номеров кластеров от 1 до 14.
  • distortions = []: Инициализирует пустой список для хранения искажения (инерции) для каждого значения k.

Кластеризация k-средних для каждого k:

  • Цикл перебирает каждое значение k.
  • kmeans = KMeans(n_clusters=k): инициализирует модель кластеризации K-средних с текущим k.
  • kmeans.fit(X) подгоняет модель под масштабированные данные.
  • distortions.append(kmeans.inertia_): Добавляет в список инерцию (сумму квадратов расстояний выборок до ближайшего центра кластера).

Применение локтевого метода:

  • kl = KneeLocatorkl = KneeLocator(K, distortions, curve="convex", direction="decreasing"): инициализирует объект KneeLocator, который помогает найти точку «колена» на кривой искажения.
  • K: Диапазон номеров кластеров.
  • distortions.
  • curve="convex": Задает тип кривой как выпуклый, как это обычно наблюдается в методе колена.
  • direction="decreasing": Указывает, что значения искажения уменьшаются.

Определение оптимального количества кластеров c:

  • c = kl.elbow: Извлекает оптимальное количество кластеров, используя точку колена/локтя, определенную с помощью KneeLocator.

Выпуск:

  • print("Optimum Clusters: ", c) Отображает оптимальное количество кластеров.

Оптимальные кластеры: 6

Объяснение локтевого метода:

  • Локтевой метод предполагает построение графиков значений искажений для различных значений k и выявление точки, где скорость снижения резко меняется, напоминая «локоть» на графике.
  • Оптимальное количество кластеров часто происходит там, где искажение начинает уменьшаться более медленными темпами (после точки локтя).
  • KneeLocator помогает автоматизировать определение этой точки локтя на кривой.

Подводя итог, можно сказать, что данная часть кода представляет собой системный подход к нахождению оптимального числа кластеров для алгоритма K-средних, повышающий точность и эффективность процесса кластеризации. Оптимальный номер кластера (c) затем используется на последующих шагах кода.

Шаг 12: Визуализация результатов кластеризации K-средних

# Visualizing K-Means Clustering Results
# …

# Fit K-Means Model
k_means = KMeans(n_clusters=6)
# k_means = KMeans(n_clusters=c)
k_means.fit(X)
prediction = k_means.predict(df_scaled)

Инициализация K-средних:

  • k_means = KMeans(n_clusters=6) инициализирует модель кластеризации K-средних с предопределенным числом кластеров (в данном случае 6).
  • В качестве альтернативы закомментированная строка (# k_means = KMeans(n_clusters=c) предлагает использовать оптимальное количество кластеров (c), определенное на предыдущем шаге.

Подгонка модели:

  • k_means.fit(X) Подгоняет модель K-средних к масштабированным данным (X).

Предсказание:

  • prediction = k_means.predict(df_scaled) прогнозирует метки кластера для каждой точки данных в масштабируемом наборе данных. Эти метки представляют кластер, которому назначена каждая точка данных.

Далее код визуализирует результаты кластеризации:# Show 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()

Центроиды и точечная диаграмма:

  • centroids = k_means.cluster_centers_: Извлекает координаты центроидов кластера.
  • fig = plt.figure(figsize=(18, 10)): Создает фигуру для построения.
  • ax = fig.add_subplot(111): Добавляет к фигуре подсюжет.
  • scatter 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"): установка меток для осей X и Y.
  • plt.colorbar(scatter): Добавляет цветовую полосу к графику.
  • plt.plot(centroids[:, 0], centroids[:, 1], "sg", markersize=10) Отображает центроиды кластера в виде зеленых квадратов.

Отобразим график:

  • plt.show(): Отображает окончательный график кластеризации.

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

K_Means Результаты кластерного анализа крипторынка

С полным кодом можно ознакомиться здесь — https://patreon.com/pppicasso

Шаг 13: Создание DataFrame с информацией о кластере

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]
clustered_series[:7]

  • Созданы две серии панд: clustered_series и clustered_series_all. Оба инициализируются метками кластера, назначенными K-средними.
  • Затем код отфильтровывает точки с меткой -1 (которые могут быть выбросами или неназначенными точками).
  • В результате получается ряд (clustered_series), содержащий метки кластера для каждой точки данных.

Шаг 14: Создание DataFrame с символом и номером кластера:

df = pd.DataFrame(clustered_series).reset_index()
df.columns = [«symbol», «cluster_number»]
df[‘symbol’] = df[‘symbol’].astype(str) + ‘/USDT:USDT’
df = df.sort_values(by=»cluster_number»)

  • Преобразует clustered_series в DataFrame (df) со столбцами «symbol» и «cluster_number».
  • Изменяет столбец «symbol», добавляя суффикс «/USDT:USDT».
  • Сортирует DataFrame на основе «cluster_number».

Группировка символов по кластеру:grouped_data = df.groupby(‘cluster_number’)[‘symbol’].agg(lambda x: ‘, ‘.join(f'»{symbol}»‘ for symbol in x))

  • Группирует DataFrame по столбцу «cluster_number».
  • Агрегирует символы в каждом кластере в строку, разделенную запятыми.

Преобразование сгруппированных данных в словарь:cluster_lists = grouped_data.to_dict()

Преобразует сгруппированные данные в словарь (cluster_lists) для быстрого доступа.

Шаг 15: Печать и запись в файл:

for cluster_number, symbol_list in cluster_lists.items():
print(f’Cluster {cluster_number}: [{symbol_list}]’)

with open(‘./clustered_data_binance_futures/Binance_futures_SimilarVolatileAssets_cluster_data.txt’, ‘w’) as file:
for cluster_number, symbol_list in cluster_lists.items():
file.write(f’Cluster {cluster_number}: [{symbol_list}]\n’)

  • Печатает кластеры и соответствующие им символы.
  • Записывает информацию о кластере в текстовый файл (Binance_futures_SimilarVolatileAssets_cluster_data.txt).

Таким образом, этот код берет результаты кластеризации K-средних, организует символы в кластеры и печатает/записывает эту информацию для дальнейшего анализа или справки.

Кластеризованные криптоактивы на основе волатильности

С полным кодом можно ознакомиться здесь — https://patreon.com/pppicasso

Шаг 16: Просмотр количества объектов в каждом кластере

plt.figure(figsize=(10, 5))
plt.bar(range(len(clustered_series.value_counts())), clustered_series.value_counts())
plt.title(«Clusters»)
plt.xlabel(«Cluster»)
plt.ylabel(«Features Count»)
plt.show()

  • Этот код использует Matplotlib для создания линейчатой диаграммы.
  • Ось X представляет номера кластеров, а ось Y представляет количество объектов (точек данных) в каждом кластере.
  • Он обеспечивает визуальное представление о том, как точки данных распределены по разным кластерам.
Линейчатая диаграмма для отображения кластеризованных криптоактивов для каждого кластера

Шаг 17: Удаление элементов, если это необходимо

# Removing items if preferred
clusters_clean = clustered_series[clustered_series < 4]
print(«Feature Number Previous: «, len(clustered_series))
print(«Feature Number Current: «, len(clusters_clean))

  • Создает новый ряд (clusters_clean) путем фильтрации точек данных с метками кластера меньше 4.
  • Выводит количество объектов до и после процесса удаления.
  • Этот шаг может быть полезен, если вы хотите исключить или сосредоточиться на определенных кластерах на основе определенного критерия (в данном случае те, у которых метки кластеров меньше 4).

Таким образом, первая часть визуально представляет распределение объектов по кластерам, а вторая часть отфильтровывает конкретные кластеры на основе условия, предоставляя информацию о количестве объектов до и после удаления.

Шаг 18: Совместная интеграция

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

Вот краткое объяснение коинтеграции и ее преимуществ:

Коинтеграция:

  • Определение: Два временных ряда называются коинтегрированными, если их линейная комбинация стационарна, то есть не имеет тренда.
  • Стационарность: В то время как отдельные временные ряды могут иметь тренды, их комбинация (коинтегрированный ряд) — нет. Это ценно, потому что подразумевает стабильные, долгосрочные отношения.
  • Пример: Рассмотрим две акции, А и В, которые являются коинтегрированными. Даже если цена каждой акции по отдельности может увеличиваться или уменьшаться с течением времени, линейная комбинация их цен (например, цена А минус константа, умноженная на цену В) остается неизменной.

Преимущества коинтеграции в трейдинге:

  1. Парный трейдинг: Коинтеграция часто используется в стратегиях парного трейдинга. Трейдеры идентифицируют коинтегрированные пары активов, ожидая, что любое краткосрочное отклонение от их исторических отношений в конечном итоге вернется к среднему значению. Этим можно воспользоваться, одновременно заняв длинную позицию по недооцененному активу и короткую позицию по переоцененному.
  2. Управление риском: Понимание коинтеграции помогает в управлении рисками. Когда два актива объединены, трейдер может использовать исторические отношения между ними для оценки потенциальных будущих движений. Эта информация может служить основой для принятия решений о размере позиции и подверженности риску.
  3. Диверсификация портфеля: Коинтеграционный анализ может быть применен для создания диверсифицированных портфелей. Выбирая активы, которые не интегрированы друг с другом, можно создать портфель, чтобы минимизировать риск и получить различные источники дохода.
  4. Стратегии возврата к среднему значению: Коинтеграция обеспечивает статистическую основу для стратегий возврата к среднему значению. Трейдеры могут воспользоваться краткосрочными отклонениями от долгосрочных отношений между коинтегрированными активами, ожидая, что они вернутся к среднему историческому значению.
  5. Хеджирование: Коинтегрированные активы могут быть использованы в целях хеджирования. Например, если инвестор владеет портфелем акций и хочет застраховаться от рыночного риска, он может использовать коинтегрированные активы для построения стратегии хеджирования.

Таким образом, коинтеграция является ценной концепцией в количественных финансах, которая помогает трейдерам и инвесторам определять стабильные отношения между активами, что приводит к различным торговым и инвестиционным стратегиям, направленным на использование или управление этими отношениями для получения финансовой выгоды.# Calculate cointegration
def calculate_cointegration(series_1, series_2):
coint_flag = 0
coint_res = coint(series_1, series_2)
coint_t = coint_res[0]
p_value = coint_res[1]
critical_value = coint_res[2][1]
model = sm.OLS(series_1, series_2).fit()
hedge_ratio = model.params[0]
coint_flag = 1 if p_value < 0.05 and coint_t < critical_value else 0
return coint_flag, hedge_ratio

  • Эта функция (calculate_cointegration) принимает два временных ряда (series_1 и series_2) в качестве входных данных и проверяет наличие коинтеграции.
  • Он использует функцию coint из библиотеки statsmodels для выполнения теста на коинтеграцию.
  • p-значение и критическое значение извлекаются из результатов теста на коинтеграцию.
  • Кроме того, он подходит для регрессионной модели по методу наименьших квадратов (МНК) для оценки коэффициента хеджирования.

# Loop through and calculate cointegrated pairs
# Allow 10 — 30 mins for calculation
tested_pairs = []
cointegrated_pairs = []

if not load_coint_pairs:
for base_asset in clusters_clean.index:
base_label = clusters_clean[base_asset]

for compare_asset in clusters_clean.index:
compare_label = clusters_clean[compare_asset]

test_pair = base_asset + compare_asset
test_pair = ».join(sorted(test_pair))
is_tested = test_pair in tested_pairs
tested_pairs.append(test_pair)

if compare_asset != base_asset and base_label == compare_label and not is_tested:

series_1 = data[base_asset].values.astype(float)
series_2 = data[compare_asset].values.astype(float)
coint_flag, _ = calculate_cointegration(series_1, series_2)
if coint_flag == 1:
cointegrated_pairs.append({«base_asset»: base_asset,
«compare_asset»: compare_asset,
«label»: base_label})

df_coint = pd.DataFrame(cointegrated_pairs).sort_values(by=»label»)
df_coint.to_csv(file_name_coint)

  • Эта часть кода циклически перебирает каждую пару ресурсов в одном кластере.
  • Он проверяет, была ли пара уже протестирована, чтобы избежать избыточности.
  • Для каждой непроверенной пары в пределах одного кластера он вычисляет коинтеграцию с помощью функции calculate_cointegration.
  • Если обнаружена коинтеграция (coint_flag равно 1), пара добавляется в список cointegrated_pairs.
  • Наконец, результирующие коинтегрированные пары сохраняются в DataFrame (df_coint) и сохраняются в CSV-файле (file_name_coint).

# Load Cointegrated Pairs
df_coint = pd.read_csv(file_name_coint).iloc[:, 1:]
df_coint.head(46)

Интегрированные пары

С полным кодом можно ознакомиться здесь — https://patreon.com/pppicasso

Краткое содержание Кодекса:

Предоставленный код описывает комплексный подход к разработке и оптимизации алгоритмических торговых стратегий, уделяя особое внимание рынкам криптовалют. К ключевым компонентам относятся:

Сбор данных:

  • Получение исторических данных о ценах на набор криптоактивов с биржи Binance.
  • Сохранение данных в CSV-файле для дальнейшего анализа.

Проектирование признаков:

  • Извлечение релевантных характеристик, таких как доходность и волатильность, из ценовых данных.
  • Масштабирование объектов с помощью StandardScaler.

Кластеризация с помощью K-средних:

  • Группировка активов в кластеры на основе их исторической доходности и волатильности.
  • Определение оптимального количества кластеров локтевым методом.
  • Применение кластеризации K-средних для категоризации активов по отдельным группам.

Анализ коинтеграции:

  • Оценка коинтеграции между парами активов в рамках одного кластера.
  • Выявление пар, которые демонстрируют стабильные, долгосрочные отношения.

Визуализация:

  • Визуализация результатов кластеризации и коинтегрированных пар для получения аналитической информации.

Следующие шаги — Скрытая марковская модель (HMM):

  • Введение концепции скрытых марковских моделей для дальнейшего анализа.
  • Использование HMM для определения и торговли только при благоприятных рыночных условиях, повышая эффективность стратегии.

Интеграция с FreqTrade:

  • Конечная цель состоит в том, чтобы интегрировать эти стратегии и идеи в бота FreqTrade.
  • Внедрение методологий обучения с подкреплением для автоматической торговли.

Преимущества и направления на будущее:

  • Парная торговля: Анализ коинтеграции позволяет определить пары, подходящие для стратегий парной торговли.
  • Управление рисками: Понимание взаимосвязей между активами помогает управлять рисками и оптимизировать диверсификацию портфеля.
  • Возврат к среднему значению: Стратегии извлекают выгоду из краткосрочных отклонений от долгосрочных отношений между коинтегрированными активами.
  • Хеджирование: Для эффективного хеджирования можно использовать коинтегрированные активы.

По отношению к FreqAI Bot и Patreon:

  • Конечная цель состоит в том, чтобы использовать эти стратегии в боте FreqTrade для автоматической торговли.
  • Методологии обучения с подкреплением повысят способность бота адаптироваться и совершенствоваться с течением времени.
  • Об успешной работе бота свидетельствует его успешность работы.
  • Доступ к эксклюзивным кодам и продвинутым стратегиям предлагается через страницу Patreon, предоставляющую сообщество для энтузиастов и сторонников.

Следующие шаги:

  • Продолжайте совершенствовать стратегии, внедряя HMM и оптимизируя торгового бота.
  • Регулярно обновляйте и делитесь информацией на странице Patreon для вовлечения и поддержки сообщества.

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

С полным кодом можно ознакомиться здесь — https://patreon.com/pppicasso

Часть 2. Скрытая марковская модель — HMM

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

Введение:

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

Скрытые марковские модели, уходящие корнями в теорию вероятностей, обеспечивают основу для понимания систем со скрытыми состояниями. В финансах эти скрытые состояния могут представлять различные рыночные режимы — бычий, медвежий или ранжирование. Различая эти состояния, HMM предлагают тонкую перспективу, позволяя трейдерам адаптировать стратегии на основе текущих рыночных условий.

Теперь наши поиски принимают конкретный оборот, поскольку мы применяем HMM для улучшения алгоритмических торговых стратегий. Основное внимание в этом анализе уделяется тщательному сравнению стратегии, подкрепленной скрытыми марковскими моделями, и стратегией, основанной только на гипероптимизации. Полем битвы для этого сравнения выбран рынок биткоина, использующий 15-минутный временной интервал, охватывающий период с 1 января 2021 года по 22 октября 2023 года — тот самый холст данных, на котором мы ранее тестировали стратегию гипероптимизации.

Цель ясна: сравнить эффективность стратегии HMM-enhanced со стратегией Hyper-Optimized и простым подходом «Купи и держи» в течение указанного периода. Эта тройка сравнений направлена на то, чтобы выяснить, может ли интеграция скрытых моделей Маркова в алгоритмические торговые стратегии опередить традиционные подходы и превосходит ли она гипероптимизированную стратегию.

Присоединяйтесь к нам в этом путешествии, когда мы раскроем потенциал HMM в изменении ландшафта алгоритмической торговли и ответим на ключевой вопрос — является ли скрытая модель Маркова ключом к большему успеху в волатильных сферах торговли криптовалютой? Ответы ждут нас по мере того, как мы будем перемещаться по данным и расшифровывать замысловатый танец динамики рынка.

Источник изображения: поиск Google, Скрытая модель Маркова

Наш путь алгоритмической торговли против машинного обучения + на данный момент?

Этап 1:

Мы разработали криптоалгоритмическую стратегию, которая принесла нам огромную прибыль при работе с несколькими криптоактивами (138+) с диапазоном прибыли 8787%+ в течение 3 лет (почти).

«Алгоритмическая стратегия ROI 8787%+ представлена для криптовалютных фьючерсов! Революция со знаменитыми RSI, MACD, полосами Боллинджера, ADX, EMA» — ссылка

Мы проводили живую торговлю в режиме пробного запуска в течение 7 дней, и подробностями об этом мы поделились в другой статье.

«Раскрыт Freqtrade: 7-дневное путешествие в алгоритмической торговле на рынке фьючерсов на криптовалюту» — ссылка

После успешных результатов тестирования на истории и форвард-тестирования (реальная торговля в режиме пробного запуска) мы планировали повысить шансы на получение большей прибыли за то же самое. (Чтобы снизить стоп-лоссы, увеличить шансы на выигрыш, уменьшить фактор риска и другие важные вещи)

Этап 2:

Мы работали над разработкой стратегии в одиночку без настройки freqtrade (избегая трейлинг-стоп-лосса, нескольких параллельных запусков, более высоких настроек управления рисками, которые freqtrade предоставляет бесплатно (это бесплатная платформа с открытым исходным кодом), а затем протестировали ее на рынке, затем оптимизировали ее с помощью гиперпараметров, а затем получили некоторую +ve прибыль от стратегии

Этап 3:

Так как мы протестировали нашу стратегию только на 1 Активе, т.е.; BTC/USDT на крипторынке, мы хотели узнать, можем ли мы разделить все коллективные активы, которые у нас есть (которые мы использовали для разработки стратегии Freqtrade ранее), разделить их на разные кластеры в зависимости от их волатильности, становится легко торговать определенными волатильными активами и не будет иметь огромных стоп-лоссов для других, если работать над реализацией на основе волатильности монет.

Мы использовали K-nearest Neighbors (KNN Means) для определения различных кластеров активов из 138 криптоактивов, которые мы используем в нашей стратегии freqtrade, что дало нам 8000+% прибыли во время тестирования на истории.

Этап 4:

Теперь мы хотим внедрить модель неконтролируемого машинного обучения — скрытую марковскую модель (HMM), чтобы выявлять тренды на рынке и торговать только во время прибыльных трендов и избегать внезапных пампов, дампов на рынке, избегать негативных тенденций на рынке. Приведенное ниже объяснение раскрывает то же самое. Давайте разбираться

Объяснение кода

Шаг 1: Импорт библиотек

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from pyhhmm.gaussian import GaussianHMM
from pandas_datareader.data import DataReader
import json
from datetime import datetime
import talib as ta
import ccxt

  • pandas и numpy для работы с данными.
  • matplotlib.pyplot для построения графиков.
  • GaussianHMM из pyhhmm для реализации скрытой марковской модели (HMM).
  • DataReader из pandas_datareader.data для получения финансовых данных.
  • json для обработки JSON.
  • datetime для работы с датами.
  • talib как ta для функций технического анализа.
  • ccxt для подключения к криптовалютной бирже.

Шаг 2: Извлечение, сегрегация и предварительная обработка данных

# Data Extraction
# start_date = «2017-01-1»
# end_date = «2022-06-1»
# symbol = «SPY»
# data = DataReader(name=symbol, data_source=’yahoo’, start=start_date, end=end_date)
# data = data[[«Open», «High», «Low», «Adj Close»]]

# Define the path to your JSON file
file_path = «../BTC_USDT_USDT-15m-futures.json»

# Open the file and read the data
with open(file_path, «r») as f:
data = json.load(f)

# Check the data structure
print(data) # Should be a list of dictionaries

# jupyter notebook —NotebookApp.iopub_data_rate_limit=100000000

df = pd.DataFrame(data)

# Extract the OHLC data (adjust column names as needed)
# ohlc_data = df[[«date»,»open», «high», «low», «close», «volume»]]
df.rename(columns={0: «Date», 1: «Open», 2: «High»,3: «Low», 4: «Adj Close», 5: «Volume»}, inplace=True)

# Convert timestamps to datetime objects
df[«Date»] = pd.to_datetime(df[‘Date’] / 1000, unit=’s’)

df.set_index(«Date», inplace=True)

# Format the date index
df.index = df.index.strftime(«%m-%d-%Y %H:%M»)

# print(df.dropna(), df.describe(), df.info())

data = df

data

Этот код, по сути, считывает данные фьючерсов BTC/USDT из файла JSON, обрабатывает их в Pandas DataFrame и форматирует DataFrame для представления данных OHLCV.

Вы можете использовать любого поставщика данных по вашему выбору, чтобы извлечь данные по мере необходимости, а затем отформатировать их в имена, как указано выше, таким образом, вам будет легко реплицировать все следующие шаги гораздо более простым способом.# Add Returns and Range
df = data.copy()
df[«Returns»] = (df[«Adj Close»] / df[«Adj Close»].shift(1)) — 1
df[«Range»] = (df[«High»] / df[«Low»]) — 1
df[«Volatility»] = df[‘Returns’].rolling(window=14).std()
df.dropna(inplace=True)
print(«Length: «, len(df))
df

Возвращает: Представляет собой ежедневную процентную доходность цены «Adj Close».

df["Returns"] = (df["Adj Close"] / df["Adj Close"].shift(1)) - 1

Диапазон: Представляет дневной процентный диапазон, рассчитываемый как разница между дневными «максимальными» и «минимальными» ценами.

df["Range"] = (df["High"] / df["Low"]) - 1

Летучесть: Представляет собой скользящее стандартное отклонение столбца «Доходность» за 14-дневное окно, обеспечивающее меру волатильности цены.

df["Volatility"] = df['Returns'].rolling(window=14).std()

Затем метод dropna используется для удаления всех строк с пропущенными значениями. Отображается окончательный DataFrame, и печатается его длина.

Данные после обработки

Шаг 3: Представляем стратегию

# Add Moving Average
df[«MA_12»] = df[«Adj Close»].rolling(window=12).mean()
df[«MA_21»] = df[«Adj Close»].rolling(window=21).mean()

def trade_signal(dataframe=df, rsi_tp=19, bb_tp=16, vol_long=42, vol_short=29):
# Compute indicators

dataframe[‘RSI’] = ta.RSI(dataframe[‘Adj Close’], timeperiod=rsi_tp)
dataframe[‘upper_band’], dataframe[‘middle_band’], dataframe[‘lower_band’] = ta.BBANDS(dataframe[‘Adj Close’], timeperiod=bb_tp)
dataframe[‘macd’], dataframe[‘signal’], _ = ta.MACD(dataframe[‘Adj Close’])

conditions_long = ((dataframe[‘RSI’] > 50) &
(dataframe[‘Adj Close’] > dataframe[‘middle_band’]) &
(dataframe[‘Adj Close’] < dataframe[‘upper_band’]) &
(dataframe[‘macd’] > dataframe[‘signal’]) &
((dataframe[‘High’] — dataframe[‘Adj Close’]) < (dataframe[‘Adj Close’] — dataframe[‘Open’])) &
(dataframe[‘Adj Close’] > dataframe[‘Open’]) &
(dataframe[‘Volume’] > dataframe[‘Volume’].rolling(window=vol_long).mean()))

conditions_short = ((dataframe[‘RSI’] < 50) &
(dataframe[‘Adj Close’] < dataframe[‘middle_band’]) &
(dataframe[‘Adj Close’] > dataframe[‘lower_band’]) &
(dataframe[‘macd’] < dataframe[‘signal’]) &
((dataframe[‘Adj Close’] — dataframe[‘Low’]) < (dataframe[‘Open’] — dataframe[‘Adj Close’])) &
(dataframe[‘Adj Close’] < dataframe[‘Open’]) &
(dataframe[‘Volume’] > dataframe[‘Volume’].rolling(window=vol_short).mean()))

dataframe[‘trend’] = 0
dataframe.loc[conditions_long, ‘trend’] = 1
dataframe.loc[conditions_short, ‘trend’] = -1

dataframe.dropna(inplace=True)

return dataframe

# ho_15m_pf = {‘bb_tp’: 16, ‘leverage’: 5, ‘rsi_tp’: 19,
# ‘stop_loss’: 0.16945844874195432, ‘take_profit’: 0.266730306293752,
# ‘vol_long’: 42, ‘vol_short’: 29}

# trading_signal = trade_signal(dataframe=df, rsi_tp=19, bb_tp=16, vol_long=42, vol_short=29)
df[‘trade_signal’] = trade_signal(dataframe=df, rsi_tp=19, bb_tp=16, vol_long=42, vol_short=29)[‘trend’]

df.info()

# Check for inf or nan values
inf_mask = np.isinf(df) | np.isnan(df)

# Remove rows containing inf or nan values
df_cleaned = df[~inf_mask.any(axis=1)]

# Remove columns containing inf or nan values
df = df_cleaned.loc[:, ~inf_mask.any(axis=0)]
df.dropna(inplace=True)

df.info()

Скользящие средние:

  • Две скользящие средние, «MA_12» и «MA_21», рассчитываются с окнами в 12 и 21 день соответственно.

df[«MA_12»] = df[«Adj Close»].rolling(window=12).mean()
df[«MA_21»] = df[«Adj Close»].rolling(window=21).mean()

Функция торгового сигнала:

  • Определена функция с именем trade_signal, которая принимает DataFrame в качестве входных данных и рассчитывает различные технические индикаторы, такие как RSI, полосы Боллинджера и MACD.
  • Торговые условия определяются как для длинных, так и для коротких позиций на основе рассчитанных индикаторов.
  • В DataFrame создается столбец trend, где 1 представляет сигнал на покупку, -1 — сигнал на продажу, а 0 — отсутствие сигнала.
  • Функция возвращает измененный DataFrame.

df[‘trade_signal’] = trade_signal(dataframe=df, rsi_tp=19, bb_tp=16, vol_long=42, vol_short=29)[‘trend’]

Очистка данных:

Код проверяет и удаляет строки и столбцы, содержащие значения NaN или inf, из DataFrame.inf_mask = np.isinf(df) | np.isnan(df)
df_cleaned = df[~inf_mask.any(axis=1)]
df = df_cleaned.loc[:, ~inf_mask.any(axis=0)]
df.dropna(inplace=True)

Информация об кадре данных

Шаг 4: Разделение и округление данных

import math

# Assuming int_data_length is the result of len(df) * 0.8
int_data_length = int(len(df) * 0.4)

# Specify the desired multiple
multiple = 1000 # You can change this to your desired multiple

# Round up to the nearest multiple
rounded_length = math.ceil(int_data_length / multiple) * multiple

# Print the result
print(rounded_length)

# int_data_length = int(len(df)* 0.8)
X_train = df[[«Returns», «Range», «Volatility»]].iloc[:rounded_length]
X_test = df[[«Returns», «Range», «Volatility»]].iloc[rounded_length:]
X = df[[«Returns», «Range», «Volatility»]]
save_df = df.iloc[rounded_length:]

print(«Train Length: «, len(X_train))

print(«Test Length: «, len(X_test))
print(«X_train From: «, X_train.head(1).index.item())
print(«X_train To: «, X_train.tail(1).index.item())
print(«X_test From: «, X_test.head(1).index.item())
print(«X_test To: «, X_test.tail(1).index.item())

Этот сегмент кода включает в себя разделение данных и округление до ближайшего кратного. Вот разбивка:

Длина округления:

  • Вычисляется указанная доля (40%) длины DataFrame ‘df’ и округляется до ближайшего кратного (указанного как 1000).

int_data_length = int(len(df) * 0.4)
rounded_length = math.ceil(int_data_length / multiple) * multiple

Разделение данных:

  • DataFrame разбивается на обучающий (X_train) и проверочный (X_test) наборы в зависимости от округленной длины.
  • Исходный кадр данных ‘X’ также определен для справки.
  • ‘save_df’ содержит оставшуюся часть DataFrame после разделения.

X_train = df[[«Returns», «Range», «Volatility»]].iloc[:rounded_length]
X_test = df[[«Returns», «Range», «Volatility»]].iloc[rounded_length:]
X = df[[«Returns», «Range», «Volatility»]]
save_df = df.iloc[rounded_length:]

Печатная информация:

. Распечатываются длины и индексы для обучающего и проверочного наборов.print(«Train Length: «, len(X_train))
print(«Test Length: «, len(X_test))
print(«X_train From: «, X_train.head(1).index.item())
print(«X_train To: «, X_train.tail(1).index.item())
print(«X_test From: «, X_test.head(1).index.item())
print(«X_test To: «, X_test.tail(1).index.item())

Этот код, по сути, подготавливает данные для обучения и тестирования модели, обеспечивая округленную длину для согласованности

Вывод для приведенного выше

Шаг 5: Обучение HMM-модели с тестовыми данными

# Train HMM
model = GaussianHMM(n_states=4, covariance_type=’full’, n_emissions=3)
# model.train([np.array(X_train_cleaned.values)])
model.train([np.array(X_train.values)])
model.predict([X_train.values])[0][:10]

# Make Prediction on Test Data
df_main = save_df.copy()
df_main.drop(columns=[«High», «Low»], inplace=True)

hmm_results = model.predict([X_test.values])[0]
df_main[«HMM»] = hmm_results
df_main

Инициализация модели HMM:

  • Гауссова скрытая марковская модель (HMM) инициализируется с 4 состояниями, полным ковариационным типом и 3 излучениями.

model = GaussianHMM(n_states=4, covariance_type=’full’, n_emissions=3)

Модель HMM поезда:

  • Модель обучается с помощью обучающих данных (X_train.values).

model.train([np.array(X_train.values)])

Создание прогнозов:

  • Прогнозы делаются на основе обучающих данных для получения последовательности скрытых состояний.

hmm_results = model.predict([X_train.values])[0][:10]

Делайте прогнозы на основе тестовых данных:

  • Обученная модель используется для прогнозирования последовательности скрытых состояний для тестовых данных (X_test.values).
  • Результаты добавляются в основной DataFrame (df_main) в столбце «HMM».

hmm_results = model.predict([X_test.values])[0]
df_main[«HMM»] = hmm_results

Этот код применяет гауссову скрытую марковскую модель к предоставленным финансовым данным, захватывая скрытые состояния и присваивая их столбцу «HMM» в DataFrame.

Тестирование данных У нас есть около 58k+ строк для тестирования

Шаг 6: ТЕСТИРОВАНИЕ РЫНКА С ПОМОЩЬЮ ТЕСТОВЫХ ДАННЫХ

# Add MA Signals
df_main.loc[df_main[«trend»] > 0, «MA_Signal»] = 1
df_main.loc[df_main[«trend»] <= 0, «MA_Signal»] = 0

# Add HMM Signals
favourable_states = [0,1]
hmm_values = df_main[«HMM»].values
hmm_values = [1 if x in favourable_states else 0 for x in hmm_values]
df_main[«HMM_Signal»] = hmm_values

# Add Combined Signal
df_main[«Main_Signal»] = 0
df_main.loc[(df_main[«MA_Signal»] == 1) & (df_main[«HMM_Signal»] == 1), «Main_Signal»] = 1
df_main[«Main_Signal»] = df_main[«Main_Signal»].shift(1)
df_main[«MA_Signal»] = df_main[«MA_Signal»].shift(1)

# Hold-On Returns
df_main[«lrets_holdon»] = np.log(df_main[«Adj Close»] / df_main[«Adj Close»].shift(1))
df_main[«holdon_prod»] = df_main[«lrets_holdon»].cumsum()
df_main[«holdon_prod_exp»] = np.exp(df_main[«holdon_prod»]) — 1

# Benchmark Returns
# df_main[«lrets_bench»] = np.log(df_main[«Adj Close»] / df_main[«Adj Close»].shift(1))
df_main[«lrets_bench»] = np.log(df_main[«Adj Close»] / df_main[«Adj Close»].shift(1)) * df_main[«MA_Signal»]
df_main[«bench_prod»] = df_main[«lrets_bench»].cumsum()
df_main[«bench_prod_exp»] = np.exp(df_main[«bench_prod»]) — 1

# Strategy Returns
df_main[«lrets_strat»] = np.log(df_main[«Open»].shift(-1) / df_main[«Open»]) * df_main[«Main_Signal»]
df_main[«lrets_prod»] = df_main[«lrets_strat»].cumsum()
df_main[«strat_prod_exp»] = np.exp(df_main[«lrets_prod»]) — 1

# Review Results Table
df_main.dropna(inplace=True)
df_main.tail()

# Sharpe Ratio Function
def sharpe_ratio(returns_series):
N = 365
NSQRT = np.sqrt(N)
rf = 0.01 / NSQRT
mean = returns_series.mean() * N
sigma = returns_series.std() * NSQRT
sharpe_ratio = round((mean — rf) / sigma, 2)
return sharpe_ratio

# Metrics
bench_rets = round(df_main[«bench_prod_exp»].values[-1] * 100, 1)
strat_rets = round(df_main[«strat_prod_exp»].values[-1] * 100, 1)
holdon_rets = round(df_main[«holdon_prod_exp»].values[-1] * 100, 1)

bench_sharpe = sharpe_ratio(df_main[«lrets_bench»].values)
strat_sharpe = sharpe_ratio(df_main[«lrets_strat»].values)
holdon_sharpe = sharpe_ratio(df_main[«lrets_holdon»].values)

# Print Metrics
print(«TESTING THE MARKET WITH TEST DATA»)
print(«—- —- —- —- —- —-«)
print(f»Returns Hold-on: {holdon_rets}%»)
print(f»Returns Benchmark: {bench_rets}%»)
print(f»Returns Strategy: {strat_rets}%»)
print(«—- —- —- —- —- —-«)
print(f»Sharpe Hold-on: {holdon_sharpe}»)
print(f»Sharpe Benchmark: {bench_sharpe}»)
print(f»Sharpe Strategy: {strat_sharpe}»)
print(«—- —- —- —- —- —-«)
print(«Returns start Date From: «, df_main.head(1).index.item())
print(«Returns End Date Upto: «, df_main.tail(1).index.item())

# Assuming X_train and X_test are your DataFrames
start_date = pd.to_datetime(df_main.head(1).index.item())
end_date = pd.to_datetime(df_main.tail(1).index.item())
# Calculate the difference
days_difference = (end_date — start_date).days
# Print or use the result as needed
print(f»Total number of days: {days_difference}»)
print(«—- —- —- —- —- —-«)

print(f»Total Daily Average Returns for Strategy: {round(((strat_rets/days_difference)),3)}%»)
print(f»Total Monthly Average Returns for Strategy: {round(((strat_rets/days_difference)*12),3)}%»)
print(f»Total yearly Average Returns for Strategy: {round(((strat_rets/days_difference)*365),3)}%»)

Добавляем сигналы MA (Trade_signal):

  • Код назначает сигналы (1 на покупку, 0 на удержание) на основе стратегии trade_signal (MA).

df_main.loc[df_main[«trend»] > 0, «MA_Signal»] = 1
df_main.loc[df_main[«trend»] <= 0, «MA_Signal»] = 0

Добавьте сигналы HMM:

  • Сигналы назначаются на основе предсказаний скрытой марковской модели (HMM) с учетом только благоприятных состояний (0 и 1).

favourable_states = [0, 1]
hmm_values = [1 if x in favourable_states else 0 for x in df_main[«HMM»].values]
df_main[«HMM_Signal»] = hmm_values

Добавить комбинированный сигнал:

  • Комбинированный сигнал генерируется на основе сигналов MA и HMM.

df_main[«Main_Signal»] = 0
df_main.loc[(df_main[«MA_Signal»] == 1) & (df_main[«HMM_Signal»] == 1), «Main_Signal»] = 1
df_main[«Main_Signal»] = df_main[«Main_Signal»].shift(1)
df_main[«MA_Signal»] = df_main[«MA_Signal»].shift(1)

Рассчитать доходность:

  • Логарифмическая доходность рассчитывается для холдинга, бенчмарка и стратегии.

df_main[«lrets_holdon»] = np.log(df_main[«Adj Close»] / df_main[«Adj Close»].shift(1))
df_main[«lrets_bench»] = np.log(df_main[«Adj Close»] / df_main[«Adj Close»].shift(1)) * df_main[«MA_Signal»]
df_main[«lrets_strat»] = np.log(df_main[«Open»].shift(-1) / df_main[«Open»]) * df_main[«Main_Signal»]

Расчет метрик производительности:

  • Коэффициенты Шарпа и доходность рассчитываются для удержания, бенчмарка и стратегии.

def sharpe_ratio(returns_series): # Sharpe Ratio Function
# …
bench_rets = round(df_main[«bench_prod_exp»].values[-1] * 100, 1)
strat_rets = round(df_main[«strat_prod_exp»].values[-1] * 100, 1)
holdon_rets = round(df_main[«holdon_prod_exp»].values[-1] * 100, 1)

Распечатать метрики:

  • Выводятся окончательные показатели, включая доходность, коэффициенты Шарпа и другую соответствующую информацию.

print(«TESTING THE MARKET WITH TEST DATA»)
# … (printing other metrics)

Этот код оценивает эффективность торговой стратегии на основе комбинированных сигналов от скользящих средних и скрытых моделей Маркова, предоставляя такие метрики, как доходность и коэффициенты Шарпа.

Результат, который мы получили из тестовых данных

С полным кодом можно ознакомиться здесь — https://patreon.com/pppicasso

Общая интерпретация:

  • Комбинированная стратегия превзошла как стратегии удержания, так и стратегии MA с точки зрения общей доходности и доходности с поправкой на риск (коэффициент Шарпа).
  • Положительные коэффициенты Шарпа указывают на то, что стратегия приносит доходность с лучшей эффективностью с поправкой на риск.
  • Похоже, что эта стратегия обеспечила более благоприятный результат по сравнению с простым удержанием или следованием только стратегии МА в течение указанного периода.

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

Шаг 7: Постройте кривые эквити для тестовых данных

# Plot Equity Curves
fig, ax = plt.subplots(figsize=(30, 15))

# Plot Hold-on Strategy
ax.plot(df_main.index, df_main[«holdon_prod_exp»] * 100, color=»red», label=»Hold-on Strategy»)

# Plot Benchmark Strategy
ax.plot(df_main.index, df_main[«bench_prod_exp»] * 100, color=»blue», label=»Benchmark Strategy»)

# Plot Combined Strategy
ax.plot(df_main.index, df_main[«strat_prod_exp»] * 100, color=»green», label=»Combined Strategy»)

# Label the x-axis and y-axis
ax.set_xlabel(‘Time’)
ax.set_ylabel(‘Profit %’)

# Add a legend
ax.legend()

# Show the plot
plt.show()

Построение графика результатов испытаний

Шаг 8: Сохранение модели и всех данных для дальнейшего использования

import joblib
joblib.dump(model, ‘hmm_model.joblib’)

# Reset the index to make «Date» a regular column
df_main = df.copy()
df_main.drop(columns=[«High», «Low»], inplace=True)

# Load the trained HMM model
loaded_model = joblib.load(‘hmm_model.joblib’)

hmm_results = loaded_model.predict([X.values])[0]
df_main[«HMM»] = hmm_results
dataset = df_main.copy()

dataset.to_csv(‘./data/dataset.csv’, index=False)

dataset

  • Обученная HMM-модель сохраняется с помощью joblib с именем файла ‘hmm_model.joblib’. В дальнейшем мы можем напрямую использовать сохраненную модель для тестирования или реальной торговли где-либо.
  • Набор данных, включая результаты HMM, сохраняется в CSV-файле с именем dataset.csv. Он содержит полные данные Обучение + Тест.

Тестирование стратегии при восходящем, нисходящем тренде и на протяжении всего рыночного цикла

Шаг 9: Сохранение данных для восходящего, нисходящего и бокового рынков

max_adj_close_idx = df_main[‘Adj Close’].idxmax()
max_adj_close_idx
# df_main.loc[’03-28-2022 19:15′]

# start_date = ’01-01-2021 00:00:00′
max_adj_close_idx

df_filtered_1 = df_main.loc[:max_adj_close_idx].copy()
df_filtered_2 = df_main.loc[max_adj_close_idx:].copy()

min_adj_close_idx_df_fintered_1 = df_filtered_1[‘Adj Close’].idxmin()
min_adj_close_idx_df_fintered_1
# df_filtered_1.loc[’02-24-2022 05:30′]
# start_date = min_adj_close_idx_2

dataset1 = df_filtered_1.loc[min_adj_close_idx_df_fintered_1:max_adj_close_idx].copy()

# Save the datasets to separate CSV files
dataset1.to_csv(‘./data/dataset1.csv’, index=False)

# Set «Date» as the index again
# dataset1.set_index(‘Date’, inplace=True)

dataset1

min_adj_close_idx_df_fintered_2 = df_filtered_2[‘Adj Close’].idxmin()

dataset2 = df_filtered_2.loc[max_adj_close_idx:min_adj_close_idx_df_fintered_2].copy()

dataset2.to_csv(‘./data/dataset2.csv’, index=False)

dataset2

# Find the mean and standard deviation of «Adj Close»
mean_adj_close = df_main[‘Adj Close’].mean()
std_dev_adj_close = df_main[‘Adj Close’].std()

# Create a mask for filtering the data within 1.5 times standard deviation from the mean
mask = (df_main[‘Adj Close’] >= mean_adj_close — 1.5 * std_dev_adj_close) & (df_main[‘Adj Close’] <= mean_adj_close + 1.5 * std_dev_adj_close)

# Find the longest continuous time dataset within the given criteria
longest_continuous_dataset = df_main[mask].copy()

longest_continuous_dataset.to_csv(‘./data/longest_continuous_dataset.csv’, index=False)

longest_continuous_dataset

Нахождение даты максимального «adj close»:

  • Код идентифицирует дату с максимальным значением «Adj Close» с помощью функции idxmax()

Разделение данных до и после максимальной даты закрытия:

  • Фрейм данных разбивается на две части: данные до даты максимального «Adj Close» и данные, начиная с этой даты.

Нахождение минимальной даты «adj close» в первом поднаборе данных:

  • Для первого вложенного набора данных (перед максимальной датой «Adj Close») код находит дату с минимальным значением «Adj Close».

Создание набора данных 1 (для восходящего тренда):

  • Набор данных (dataset1) создается путем выбора данных от минимальной даты «Adj Close» в первом вложенном наборе данных до даты максимального «Adj Close».

Сохранение набора данных 1 в CSV:

  • Первый набор данных сохраняется в CSV-файле с именем dataset1.csv для дальнейшего анализа или справки.

Нахождение минимальной даты «adj close» во втором поднаборе данных:

  • Для второго поднабора данных (начиная с максимальной даты «Adj Close» и далее) код находит дату с минимальным значением «Adj Close».

Создание набора данных 2 (для нисходящего тренда):

  • Другой набор данных (dataset2) создается путем выбора данных от максимальной даты «Adj Close» до даты минимальной «Adj Close» во втором поднаборе данных.

Сохранение набора данных 2 в CSV:

  • Второй набор данных сохраняется в CSV-файл с именем dataset2.csv для дальнейшего анализа или справки.

Фильтрация данных в пределах 1,5-кратного стандартного отклонения от среднего:

  • Код вычисляет среднее значение и стандартное отклонение значений «Adj Close» для всего фрейма данных.
  • Создается маска для фильтрации данных в пределах 1,5-кратного стандартного отклонения от среднего значения.

Нахождение самого длинного набора данных непрерывного времени в рамках критериев (для бокового рынка):

  • Код извлекает самый длинный непрерывный набор данных за время в пределах критериев, определенных маской.
  • Этот набор данных сохраняется в CSV-файле с именем «longest_continuous_dataset.csv» для дальнейшего анализа или справки.

Результаты печати:

  • Результирующие наборы данных (dataset1dataset2 и longest_continuous_dataset) распечатываются для изучения.

Шаг 10: Тестирование ВО ВРЕМЯ ВОСХОДЯЩЕГО ТРЕНДА РЫНКА

# Make Prediction on Test Data
df_main = dataset1.copy()

df_main

# Add MA Signals
df_main.loc[df_main[«trend»] > 0, «MA_Signal»] = 1
df_main.loc[df_main[«trend»] <= 0, «MA_Signal»] = 0

# Add HMM Signals
favourable_states = [0,1,2,3]
hmm_values = df_main[«HMM»].values
hmm_values = [1 if x in favourable_states else 0 for x in hmm_values]
df_main[«HMM_Signal»] = hmm_values

# Add Combined Signal
df_main[«Main_Signal»] = 0
df_main.loc[(df_main[«MA_Signal»] == 1) & (df_main[«HMM_Signal»] == 1), «Main_Signal»] = 1
df_main[«Main_Signal»] = df_main[«Main_Signal»].shift(1)
df_main[«MA_Signal»] = df_main[«MA_Signal»].shift(1)

# Hold-On Returns
df_main[«lrets_holdon»] = np.log(df_main[«Adj Close»] / df_main[«Adj Close»].shift(1))
df_main[«holdon_prod»] = df_main[«lrets_holdon»].cumsum()
df_main[«holdon_prod_exp»] = np.exp(df_main[«holdon_prod»]) — 1

# Benchmark Returns
# df_main[«lrets_bench»] = np.log(df_main[«Adj Close»] / df_main[«Adj Close»].shift(1))
df_main[«lrets_bench»] = np.log(df_main[«Adj Close»] / df_main[«Adj Close»].shift(1)) * df_main[«MA_Signal»]
df_main[«bench_prod»] = df_main[«lrets_bench»].cumsum()
df_main[«bench_prod_exp»] = np.exp(df_main[«bench_prod»]) — 1

# Strategy Returns
df_main[«lrets_strat»] = np.log(df_main[«Open»].shift(-1) / df_main[«Open»]) * df_main[«Main_Signal»]
df_main[«lrets_prod»] = df_main[«lrets_strat»].cumsum()
df_main[«strat_prod_exp»] = np.exp(df_main[«lrets_prod»]) — 1

# Review Results Table
df_main.dropna(inplace=True)
df_main.tail()

# Sharpe Ratio Function
def sharpe_ratio(returns_series):
N = 365
NSQRT = np.sqrt(N)
rf = 0.01 / NSQRT
mean = returns_series.mean() * N
sigma = returns_series.std() * NSQRT
sharpe_ratio = round((mean — rf) / sigma, 2)
return sharpe_ratio

# Metrics
bench_rets = round(df_main[«bench_prod_exp»].values[-1] * 100, 1)
strat_rets = round(df_main[«strat_prod_exp»].values[-1] * 100, 1)
holdon_rets = round(df_main[«holdon_prod_exp»].values[-1] * 100, 1)

bench_sharpe = sharpe_ratio(df_main[«lrets_bench»].values)
strat_sharpe = sharpe_ratio(df_main[«lrets_strat»].values)
holdon_sharpe = sharpe_ratio(df_main[«lrets_holdon»].values)

# Print Metrics
print(«DURING UP-TREND OF THE MARKET»)
print(«—- —- —- —- —- —-«)
print(f»Returns Hold-on (If you only entered at Lowest price of the asset during UP-Trend): {holdon_rets}%»)
print(f»Returns Benchmark: {bench_rets}%»)
print(f»Returns Strategy: {strat_rets}%»)
print(«—- —- —- —- —- —-«)
print(f»Sharpe Hold-on: {holdon_sharpe}»)
print(f»Sharpe Benchmark: {bench_sharpe}»)
print(f»Sharpe Strategy: {strat_sharpe}»)
print(«—- —- —- —- —- —-«)
print(«Returns start Date From: «, dataset1.head(1).index.item())
print(«Returns End Date Upto: «, dataset1.tail(1).index.item())

# Assuming X_train and X_test are your DataFrames
start_date = pd.to_datetime(dataset1.head(1).index.item())
end_date = pd.to_datetime(dataset1.tail(1).index.item())
# Calculate the difference
days_difference = (end_date — start_date).days
# Print or use the result as needed
print(f»Total number of days: {days_difference}»)
print(«—- —- —- —- —- —-«)

print(f»Total Daily Average Returns for Strategy: {round(((strat_rets/days_difference)),3)}%»)
print(f»Total Monthly Average Returns for Strategy: {round(((strat_rets/days_difference)*12),3)}%»)
print(f»Total yearly Average Returns for Strategy: {round(((strat_rets/days_difference)*365),3)}%»)

Описание кода аналогично описанию «STEP 6», за исключением импорта dataset1

результаты для датасета1 Во время восходящего тренда рынка

Во время восходящего тренда рынка для dataset1 были получены следующие результаты:

Возвращает Hold-on (Если вы вошли только по самой низкой цене актива во время восходящего тренда):

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

Бенчмарк доходности:

  • 35.3%
  • Это доходность, достигаемая эталонной стратегией.

Стратегия возврата:

  • 35.3%
  • Это отдача, достигаемая реализованной стратегией.

Коэффициент Шарпа Hold-on:

  • 0.1
  • Коэффициент Шарпа измеряет эффективность инвестиций с поправкой на риск. Более высокий коэффициент Шарпа указывает на лучшую доходность с поправкой на риск.

Эталон коэффициента Шарпа:

  • 0.18
  • Коэффициент Шарпа для эталонной стратегии.

Стратегия коэффициента Шарпа:

  • 0.18
  • Коэффициент Шарпа для реализуемой стратегии.

Дата начала возврата от:

  • 01–01–2021 18:30
  • Дата начала анализируемого периода.

Дата окончания возврата до:

  • 11–10–2021 14:00
  • Дата окончания анализируемого периода.

Общее количество дней:

  • 312
  • Общее количество дней, рассматриваемых в наборе данных.

Общая среднесуточная доходность стратегии:

  • 0.113%
  • Среднесуточная доходность по реализованной стратегии.

Общая среднемесячная доходность стратегии:

  • 1.358%
  • Среднемесячная доходность по реализованной стратегии.

Общая среднегодовая доходность стратегии:

  • 41.296%
  • Среднегодовая доходность по реализованной стратегии.

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

Несмотря на то, что Hold-on дает большую прибыль (только при покупке по самой низкой цене во время рыночного цикла), даже в этом случае коэффициент Шарпа меньше по сравнению с бенчмарком или стратегией, это связано с количеством сделок/дней, которые он продолжал удерживать, увеличивая риск потери денег, если рынок вообще колеблется в направлении -ve.

Одинаковая доходность для бенчмарка и стратегии говорит о том, что во время восходящего тренда оба работают почти одинаково. Мы также должны проверить, как они ведут себя во время нисходящего тренда.

Шаг 11: Построение графика результатов во время восходящего тренда

# Plot Equity Curves
fig, ax = plt.subplots(figsize=(30, 15))

# Plot Hold-on Strategy
ax.plot(df_main.index, df_main[«holdon_prod_exp»] * 100, color=»red», label=»Hold-on Strategy»)

# Plot Benchmark Strategy
ax.plot(df_main.index, df_main[«bench_prod_exp»] * 100, color=»blue», label=»Benchmark Strategy»)

# Plot Combined Strategy
ax.plot(df_main.index, df_main[«strat_prod_exp»] * 100, color=»green», label=»Combined Strategy»)

# Label the x-axis and y-axis
ax.set_xlabel(‘Time’)
ax.set_ylabel(‘Profit %’)

# Add a legend
ax.legend()

# Show the plot
plt.show()

Результаты построения графиков при восходящем тренде

Из приведенного выше графика видно, что, хотя стратегия удержания дала огромные 135% + прибыль, видно, что она сильно упала со 120%+, обратно до 0%, а затем снова выросла до 135%+, показывает, что слишком рискованно просто держать монету.

Шаг 12: Протестируйте ВО ВРЕМЯ НИСХОДЯЩЕГО ТРЕНДА РЫНКА

# Make Prediction on Test Data
df_main = dataset2.copy()

df_main

# Add MA Signals
df_main.loc[df_main[«trend»] > 0, «MA_Signal»] = 1
df_main.loc[df_main[«trend»] <= 0, «MA_Signal»] = 0

# Add HMM Signals
favourable_states = [1]
hmm_values = df_main[«HMM»].values
hmm_values = [1 if x in favourable_states else 0 for x in hmm_values]
df_main[«HMM_Signal»] = hmm_values

# Add Combined Signal
df_main[«Main_Signal»] = 0
df_main.loc[(df_main[«MA_Signal»] == 1) & (df_main[«HMM_Signal»] == 1), «Main_Signal»] = 1
df_main[«Main_Signal»] = df_main[«Main_Signal»].shift(1)
df_main[«MA_Signal»] = df_main[«MA_Signal»].shift(1)

# Hold-On Returns
df_main[«lrets_holdon»] = np.log(df_main[«Adj Close»] / df_main[«Adj Close»].shift(1))
df_main[«holdon_prod»] = df_main[«lrets_holdon»].cumsum()
df_main[«holdon_prod_exp»] = np.exp(df_main[«holdon_prod»]) — 1

# Benchmark Returns
# df_main[«lrets_bench»] = np.log(df_main[«Adj Close»] / df_main[«Adj Close»].shift(1))
df_main[«lrets_bench»] = np.log(df_main[«Adj Close»] / df_main[«Adj Close»].shift(1)) * df_main[«MA_Signal»]
df_main[«bench_prod»] = df_main[«lrets_bench»].cumsum()
df_main[«bench_prod_exp»] = np.exp(df_main[«bench_prod»]) — 1

# Strategy Returns
df_main[«lrets_strat»] = np.log(df_main[«Open»].shift(-1) / df_main[«Open»]) * df_main[«Main_Signal»]
df_main[«lrets_prod»] = df_main[«lrets_strat»].cumsum()
df_main[«strat_prod_exp»] = np.exp(df_main[«lrets_prod»]) — 1

# Review Results Table
df_main.dropna(inplace=True)
df_main.tail()

# Sharpe Ratio Function
def sharpe_ratio(returns_series):
N = 365
NSQRT = np.sqrt(N)
rf = 0.01 / NSQRT
mean = returns_series.mean() * N
sigma = returns_series.std() * NSQRT
sharpe_ratio = round((mean — rf) / sigma, 2)
return sharpe_ratio

# Metrics
bench_rets = round(df_main[«bench_prod_exp»].values[-1] * 100, 1)
strat_rets = round(df_main[«strat_prod_exp»].values[-1] * 100, 1)
holdon_rets = round(df_main[«holdon_prod_exp»].values[-1] * 100, 1)

bench_sharpe = sharpe_ratio(df_main[«lrets_bench»].values)
strat_sharpe = sharpe_ratio(df_main[«lrets_strat»].values)
holdon_sharpe = sharpe_ratio(df_main[«lrets_holdon»].values)

# Print Metrics
print(«DURING DOWN-TREND OF THE MARKET»)
print(«—- —- —- —- —- —-«)
print(f»Returns Hold-on (If you only entered at Lowest price of the asset during DOWN-Trend): {holdon_rets}%»)
print(f»Returns Benchmark: {bench_rets}%»)
print(f»Returns Strategy: {strat_rets}%»)
print(«—- —- —- —- —- —-«)
print(f»Sharpe Hold-on: {holdon_sharpe}»)
print(f»Sharpe Benchmark: {bench_sharpe}»)
print(f»Sharpe Strategy: {strat_sharpe}»)
print(«—- —- —- —- —- —-«)
print(«Returns start Date From: «, dataset2.head(1).index.item())
print(«Returns End Date Upto: «, dataset2.tail(1).index.item())

# Assuming X_train and X_test are your DataFrames
start_date = pd.to_datetime(dataset2.head(1).index.item())
end_date = pd.to_datetime(dataset2.tail(1).index.item())
# Calculate the difference
days_difference = (end_date — start_date).days
# Print or use the result as needed
print(f»Total number of days: {days_difference}»)
print(«—- —- —- —- —- —-«)

print(f»Total Daily Average Returns for Strategy: {round(((strat_rets/days_difference)),3)}%»)
print(f»Total Monthly Average Returns for Strategy: {round(((strat_rets/days_difference)*12),3)}%»)
print(f»Total yearly Average Returns for Strategy: {round(((strat_rets/days_difference)*365),3)}%»)

Описание кода такое же, как и в «STEP 6», с той лишь разницей, что мы использовали dataset2 , который предназначен для нисходящих рыночных данных.

Результаты ВО ВРЕМЯ НИСХОДЯЩЕГО ТРЕНДА РЫНКА

Результаты во время нисходящего тренда:

Доходность удержания (во время нисходящего тренда):

  • Понес значительный убыток в размере -77,1%, если входил в рынок по самой низкой цене во время нисходящего тренда и удерживал позицию.

Эталонная доходность:

  • Эталонная стратегия достигла маржинальной доходности в 3,3%, что указывает на небольшую положительную динамику в течение анализируемого периода нисходящего тренда.

Возврат стратегии:

  • Реализованная торговая стратегия принесла доходность в размере 13,2%, продемонстрировав свою способность получать положительную доходность даже на рынке с нисходящим трендом.

Коэффициенты Шарпа:

  • Коэффициенты Шарпа указывают на производительность с поправкой на риск.
  • Коэффициент Шарпа для удержания: -0,22
  • Коэффициент Шарпа для бенчмарка: -0.02
  • Коэффициент Шарпа для стратегии: 0.09
  • Положительный коэффициент Шарпа в стратегии предполагает лучшую производительность с поправкой на риск по сравнению с подходом удержания.

Общая продолжительность:

  • Анализ охватывал в общей сложности 376 дней нисходящего тренда.

Среднесуточная доходность стратегии:

  • Среднесуточная доходность стратегии составила 0,035%.

Среднемесячная доходность стратегии:

  • Среднемесячная доходность составила 0,421%.

Средняя годовая доходность стратегии:

  • Стратегия показала среднегодовую доходность 12,814%.

Заключение:

Оценка производительности:

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

Производительность с поправкой на риск:

  • Положительный коэффициент Шарпа для стратегии указывает на благоприятную производительность с поправкой на риск, предполагая, что стратегия достигла доходности при эффективном управлении рисками.

Влияние на рыночную конъюнктуру:

  • Результаты подчеркивают адаптивность стратегии к различным рыночным условиям, демонстрируя ее потенциал для получения положительной доходности даже в сложных фазах нисходящего тренда.

Общая оценка:

  • Способность стратегии обеспечивать положительную доходность как в периоды восходящего, так и в нисходящего тренда предполагает ее потенциал в качестве надежного и универсального торгового подхода.

Шаг 13: Построение графика результатов при нисходящем тренде

# Plot Equity Curves
fig, ax = plt.subplots(figsize=(30, 15))

# Plot Hold-on Strategy
ax.plot(df_main.index, df_main[«holdon_prod_exp»] * 100, color=»red», label=»Hold-on Strategy»)

# Plot Benchmark Strategy
ax.plot(df_main.index, df_main[«bench_prod_exp»] * 100, color=»blue», label=»Benchmark Strategy»)

# Plot Combined Strategy
ax.plot(df_main.index, df_main[«strat_prod_exp»] * 100, color=»green», label=»Combined Strategy»)

# Label the x-axis and y-axis
ax.set_xlabel(‘Time’)
ax.set_ylabel(‘Profit %’)

# Add a legend
ax.legend()

# Show the plot
plt.show()

График для данных во время нисходящего тренда

Способность стратегии обеспечивать положительную доходность как в периоды восходящего, так и в нисходящего тренда предполагает ее потенциал в качестве надежного и универсального торгового подхода.

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

Шаг 14: Тестируйте В ТЕЧЕНИЕ ВСЕГО ТЕСТИРУЕМОГО РЫНКА (также боковые пути) ПЕРИОД

# Make Prediction on Test Data
df_main = dataset.copy()

df_main

# Add MA Signals
df_main.loc[df_main[«trend»] > 0, «MA_Signal»] = 1
df_main.loc[df_main[«trend»] <= 0, «MA_Signal»] = 0

# Add HMM Signals
favourable_states = [0,1,3]
hmm_values = df_main[«HMM»].values
hmm_values = [1 if x in favourable_states else 0 for x in hmm_values]
df_main[«HMM_Signal»] = hmm_values

# Add Combined Signal
df_main[«Main_Signal»] = 0
df_main.loc[(df_main[«MA_Signal»] == 1) & (df_main[«HMM_Signal»] == 1), «Main_Signal»] = 1
df_main[«Main_Signal»] = df_main[«Main_Signal»].shift(1)
df_main[«MA_Signal»] = df_main[«MA_Signal»].shift(1)

# Hold-On Returns
df_main[«lrets_holdon»] = np.log(df_main[«Adj Close»] / df_main[«Adj Close»].shift(1))
df_main[«holdon_prod»] = df_main[«lrets_holdon»].cumsum()
df_main[«holdon_prod_exp»] = np.exp(df_main[«holdon_prod»]) — 1

# Benchmark Returns
# df_main[«lrets_bench»] = np.log(df_main[«Adj Close»] / df_main[«Adj Close»].shift(1))
df_main[«lrets_bench»] = np.log(df_main[«Adj Close»] / df_main[«Adj Close»].shift(1)) * df_main[«MA_Signal»]
df_main[«bench_prod»] = df_main[«lrets_bench»].cumsum()
df_main[«bench_prod_exp»] = np.exp(df_main[«bench_prod»]) — 1

# Strategy Returns
df_main[«lrets_strat»] = np.log(df_main[«Open»].shift(-1) / df_main[«Open»]) * df_main[«Main_Signal»]
df_main[«lrets_prod»] = df_main[«lrets_strat»].cumsum()
df_main[«strat_prod_exp»] = np.exp(df_main[«lrets_prod»]) — 1

# Review Results Table
df_main.dropna(inplace=True)
df_main.tail()

# Sharpe Ratio Function
def sharpe_ratio(returns_series):
N = 365
NSQRT = np.sqrt(N)
rf = 0.01 / NSQRT
mean = returns_series.mean() * N
sigma = returns_series.std() * NSQRT
sharpe_ratio = round((mean — rf) / sigma, 2)
return sharpe_ratio

# Metrics
bench_rets = round(df_main[«bench_prod_exp»].values[-1] * 100, 1)
strat_rets = round(df_main[«strat_prod_exp»].values[-1] * 100, 1)
holdon_rets = round(df_main[«holdon_prod_exp»].values[-1] * 100, 1)

bench_sharpe = sharpe_ratio(df_main[«lrets_bench»].values)
strat_sharpe = sharpe_ratio(df_main[«lrets_strat»].values)
holdon_sharpe = sharpe_ratio(df_main[«lrets_holdon»].values)

# Print Metrics
print(«DURING THE WHOLE TESTING MARKET PERIOD»)
print(«—- —- —- —- —- —-«)
print(f»Returns Hold-on (If you only entered at Start Day price of the asset during WHOLE MARKET CYCLE): {holdon_rets}%»)
print(f»Returns Benchmark: {bench_rets}%»)
print(f»Returns Strategy: {strat_rets}%»)
print(«—- —- —- —- —- —-«)
print(f»Sharpe Hold-on: {holdon_sharpe}»)
print(f»Sharpe Benchmark: {bench_sharpe}»)
print(f»Sharpe Strategy: {strat_sharpe}»)
print(«—- —- —- —- —- —-«)
print(«Returns start Date From: «, dataset.head(1).index.item())
print(«Returns End Date Upto: «, dataset.tail(1).index.item())

# Assuming X_train and X_test are your DataFrames
start_date = pd.to_datetime(dataset.head(1).index.item())
end_date = pd.to_datetime(dataset.tail(1).index.item())
# Calculate the difference
days_difference = (end_date — start_date).days
# Print or use the result as needed
print(f»Total number of days: {days_difference}»)
print(«—- —- —- —- —- —-«)

print(f»Total Daily Average Returns for Strategy: {round(((strat_rets/days_difference)),3)}%»)
print(f»Total Monthly Average Returns for Strategy: {round(((strat_rets/days_difference)*12),3)}%»)
print(f»Total yearly Average Returns for Strategy: {round(((strat_rets/days_difference)*365),3)}%»)

То же описание, что и в «ШАГЕ 6», здесь мы использовали набор данных, который содержит весь рыночный цикл, хотя я написал код для короткого списка, основанный на 1,5 стандартном отклонении (STD) от среднего значения всех данных, он также дал то же количество дней, что и целые рыночные дни, возможно, из-за того, что значения были близки друг к другу. Таким образом, мы пришли к такому же выводу для обоих из наших выводов (возможно, это нуждается в дальнейшем правильном разделении, вместо 1,5 ЗППП мы должны попробовать 1 ЗППП в следующий раз)

Результаты ЗА ВЕСЬ ПЕРИОД ТЕСТИРОВАНИЯ РЫНКА

Общие результаты:

Возвраты Hold-On (на протяжении всего периода тестирования):

  • Получил скромную доходность в 1,7%, если входил в рынок на старте и удерживал позицию.

Эталонная доходность:

  • Эталонная стратегия достигла существенной доходности в размере 59,1%.

Возврат стратегии:

  • Реализованная торговая стратегия близко отражала бенчмарк с доходностью 59,2%, что указывает на ее эффективность в улавливании рыночных тенденций.

Коэффициенты Шарпа:

  • Коэффициенты Шарпа дают представление о производительности с поправкой на риск.
  • Коэффициент Шарпа для удержания: -0,01
  • Коэффициент Шарпа для бенчмарка: 0.09
  • Коэффициент Шарпа для стратегии: 0.09
  • Положительный коэффициент Шарпа в стратегии означает благоприятную производительность с поправкой на риск по сравнению с подходом удержания.

Общая продолжительность:

  • Анализ охватывал в общей сложности 1024 дня в течение всего периода тестирования.

Среднесуточная доходность стратегии:

  • Стратегия показала среднедневную доходность 0,058%.

Среднемесячная доходность стратегии:

  • Среднемесячная доходность составила 0,694%.

Средняя годовая доходность стратегии:

  • Стратегия продемонстрировала среднегодовую доходность 21,102%.

Заключение:

Обзор производительности:

  • Торговая стратегия продемонстрировала высокую производительность, близко соответствуя эталонной доходности на протяжении всего периода тестирования.

Производительность с поправкой на риск:

  • Положительный коэффициент Шарпа указывает на то, что стратегия достигла доходности при эффективном управлении рисками, превзойдя подход удержания с точки зрения производительности с поправкой на риск.

Адаптивность к рынку:

  • Способность стратегии стабильно генерировать положительную доходность предполагает ее адаптивность к изменяющимся рыночным условиям, что способствует ее надежности.

Инвестиционное соображение:

  • Конкурентоспособность стратегии по сравнению с бенчмарком позиционирует ее как потенциальный инвестиционный подход. Дальнейшая тонкая настройка и оптимизация могут повысить его эффективность.

Общая оценка:

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

Шаг 13: Построение графика результатов для всего цикла тренда рынка

# Plot Equity Curves
fig, ax = plt.subplots(figsize=(30, 15))

# Plot Hold-on Strategy
ax.plot(df_main.index, df_main[«holdon_prod_exp»] * 100, color=»red», label=»Hold-on Strategy»)
# Plot Benchmark Strategy
ax.plot(df_main.index, df_main[«bench_prod_exp»] * 100, color=»blue», label=»Benchmark Strategy»)
# Plot Combined Strategy
ax.plot(df_main.index, df_main[«strat_prod_exp»] * 100, color=»green», label=»Combined Strategy»)
# Label the x-axis and y-axis
ax.set_xlabel(‘Time’)
ax.set_ylabel(‘Profit %’)
# Add a legend
ax.legend()
# Show the plot
plt.show()

Производительность на графике за весь рыночный цикл

С полным кодом можно ознакомиться здесь — https://patreon.com/pppicasso

Итоги интеграции кода HMM и эволюции стратегии в целом:

1. Первоначальная разработка стратегии:

Стратегия Freqtrade:

  • Начинал с традиционной алгоритмической торговой стратегии с использованием Freqtrade.
  • Провели бэк-тестирование и форвард-тестирование для оценки производительности.
  • «Алгоритмическая стратегия ROI 8787%+ представлена для криптовалютных фьючерсов! Революция со знаменитыми RSI, MACD, полосами Боллинджера, ADX, EMA» — ссылка .
  • Мы проводили живую торговлю в режиме пробного запуска в течение 7 дней, и подробностями об этом мы поделились в другой статье.
  • «Раскрыт Freqtrade: 7-дневное путешествие в алгоритмической торговле на рынке фьючерсов на криптовалюту» — ссылка
  • С полным кодом можно ознакомиться здесь — https://patreon.com/pppicasso

2. Внедрение методов машинного обучения:

Алгоритмические стратегии:

  • Реализовал индивидуальные стратегии на основе RSI, MACD и полос Боллинджера.
  • Проведена гиперпараметрическая оптимизация для повышения эффективности стратегии.
  • «Как я достиг 3000+% прибыли при тестировании на истории для различных алгоритмических торговых ботов и как вы можете сделать то же самое для своих торговых стратегий — используя код Python» — ссылка

Кластеризация с помощью KNN:

  • Использование K-Nearest Neighbors (KNN) для кластеризации криптоактивов с аналогичной волатильностью.
  • Улучшен выбор активов для конкретных стратегий.
  • «Гипероптимизированная алгоритмическая стратегия vs/+ модели машинного обучения, часть -1 (k-ближайшие соседи)» — ссылка

3. Идентификация тренда с помощью HMM:

  • Скрытая марковская модель (HMM):
  • Интегрированный HMM для определения трендовых рынков, благоприятных для стратегии.
  • Анализируются результаты во время восходящих и нисходящих трендов.

4. Планы:

Контролируемое обучение с XGBoost:

  • Запланируйте разработку и тестирование стратегии с использованием XGBoost для контролируемого обучения.

Методы глубокого обучения:

  • Экспериментируем с RNN, CNN для дальнейшего совершенствования стратегии.

Обучение с подкреплением:

  • Стремитесь интегрировать обучение с подкреплением для разработки автоматических торговых стратегий. Разработка стратегии FreqAI

5. Общая оценка:

Адаптивность и надежность:

  • Эволюция стратегии демонстрирует адаптивность и надежность.

Интеграция с несколькими техниками:

  • Сочетание традиционных алгоритмов с машинным обучением и методами глубокого обучения повышает универсальность стратегии.

Непрерывное совершенствование:

  • Постоянное изучение передовых методов для постоянного совершенствования.

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

Источник