Прогнозирование временных рядов криптовалют с Python
В обанкротившейся криптофирме FTX отсутствует, по меньшей мере, 1 миллиард долларов клиентских средств, а их токен FTX потерял большую часть своей стоимости в ноябре 2022 года. Как бы вы уберегли свой портфель от огромных потерь в случае краха?
Это руководство поможет вам понять метод очистки данных временных рядов и то, как крупные финансовые компании создают популярные индексы, такие как S &P 500 или Nasdaq. Самое главное, как создать индекс вашего портфеля, содержащий различные криптовалюты, чтобы отслеживать ваши показатели и использовать машинное обучение для прогнозирования движения индекса в ближайшем будущем.
Цель этого руководства – помочь новичку, который немного разбирается во временных рядах, но испытывает трудности с обработкой реальных наборов данных. Вы сможете быстро восполнить пробел с помощью этого руководства. Я надеюсь, что каждый сможет найти что-то полезное в нём.
@data_analysis_ml – инструменты анализа данных.
Загрузка наборов данных
Во-первых, давайте получим цену закрытия каждой криптовалюты, включая Bitcoin, Binance Coin, Dogecoin, Ethereum, USD Coin, Tether, XRP и токен FTX с 2010 года от Coincodex.
Примечание: Все цены, используемые в данных, основаны на обменном курсе между целевой валютой и долларом США.
import pandas as pd
coins = pd.read_csv("/cryptocurrency/coins.csv", sep=",", header=0)
coins.set_index("Date", inplace=True) # make the date become the index
coins = coins.sort_index()
coins
Затем нам нужно выяснить некоторые внешние факторы, которые могут повлиять на цену криптовалют. Давайте возьмём некоторые командные индексы с рынка, такие как S & P 500, Nasdaq, gold и silver, которые стартовали в 2018 году. Мы также можем добавить некоторые популярные экономические показатели, такие как ежедневные ставки номинальной доходности казначейских облигаций Министерства финансов США или CPI и PSR.
factors = pd.read_csv("/cryptocurrency/predictor_variables.csv", sep=",", header=0)
factors.set_index("Date", inplace=True) # make the date become the index
factors = factors.sort_index()
factors
Очистка данных
Когда мы получаем данные временных рядов, они лишь иногда бывают в требуемом формате. Большая часть необработанных данных либо должна быть более упорядочена, либо содержит множество отсутствующих значений или дат, что делает невозможным обучение моделей. Таким образом, понимание того, как правильно очищать и подготавливать данные, является одним из важных навыков для специалиста по обработке данных.
Очистка пропущенных значений
Во-первых, давайте проясним, что нулевые значения находятся в криптографических данных.
coins = coins.dropna()
coins
Заполнение недостающих значений
Мы не можем использовать один и тот же метод для внешних факторов, поскольку данные основаны на разной временной последовательности. Таким образом, нам нужно найти и установить первые даты данных, которые будут совпадать со всеми криптовалютами. Из предыдущего результата мы знаем, что можем установить нашу дату как 2019/08/01. Чтобы преобразовать эти данные в полезную информацию, мы сначала предположим, что в течение отсутствующих дней большинство из них приходится либо на выходные, либо на праздничные дни, и значения остаются теми же, что и накануне. Основываясь на этом предположении, мы можем заполнить недостающее время даты значениями предыдущего дня.
factors = factors["2019-08-01":]
factors = factors.reindex(pd.date_range("2019-08-01", "2022-11-15")).reset_index().rename(columns={"index": "Date"})
factors = factors.groupby(factors["Date"].dt.time).ffill() # fill the missing date values
factors.set_index("Date", inplace=True)
factors
Создание настраиваемого индекса
Мы хотим спрогнозировать индекс в этом проекте, и в какой-то момент времени t мы будем использовать простой метод индексации, называемый “Равновзвешенный индекс”. Это среднее значение по криптовалюте.
Равный вес – это тип пропорционального метода измерения, который придаёт одинаковую важность каждой криптовалюте в портфеле, индексе или индексном фонде. Таким образом, акции самой маленькой криптовалюты имеют одинаковую статистическую значимость или вес по сравнению с крупнейшими компаниями, когда дело доходит до оценки общей эффективности группы. Следующее уравнение может помочь нам достичь этой цели.
- Значение индекса (V): относится к равновзвешенному индексу.
- Цена(P): относится к цене криптовалюты.
- Вес (W): относится к присвоенному весу, но в равновзвешенном индексе каждый вес равен 1 / N, где N – количество крипты в индексе.
result = []
# calculate the index value
for i in range(len(coins.columns)):
coin = coins[coins.columns[i]] / len(coins.columns)
result.append(coin)
# assign index value with date
ew_index = pd.DataFrame(1 + pd.DataFrame(pd.concat(result, axis=1)).sum(axis=1))
ew_index.set_axis([*ew_index.columns[:-1], "Index"], axis=1, inplace=True)
ew_index.tail(5)
Здесь мы можем использовать пакет seaborn
для просмотра межквартильного диапазона индекса за каждый месяц.
import seaborn as sns
import matplotlib.pyplot as plt
ts_fig, ts_ax = plt.subplots(figsize=(36, 9))
sns.boxplot(x=ew_index.index.strftime("%Y-%b"), y=ew_index.Index, ax=ts_ax)
ts_ax.set_xlabel("Month", labelpad=9, fontsize=15)
ts_ax.set_ylabel("Total Rides", labelpad=9, fontsize=15)
ts_ax.set_xticklabels(ts_ax.get_xticklabels(), rotation=90)
ts_ax.set_title("Monthly Index", fontsize=21)
plt.show()
Предварительная обработка данных
Прежде чем мы подготовим данные индекса, мы должны нормализовать данные и проверить корреляцию между нашим индексом и внешними факторами. Давайте сначала объединим значения индекса и переменные-предикторы в один фрейм данных.
data = factors.merge(ew_index, how="left", left_on="Date", right_on="Date")
Нормализация переменных
Теперь у нас есть чистые данные, готовые к использованию. Прежде чем мы начнём обучать модель с этими данными, одним из важных шагов является обеспечение нормализации всех данных. Поскольку и наши целевые переменные, и переменные-предикторы измеряются в разных шкалах, мы должны привести значения в соответствие с условно стандартной шкалой в этом разделе. Есть много способов достичь этого, но в этом проекте будет использоваться один из самых простых методов, нормализация Min-Max, которая заключается в масштабировании диапазона объектов для масштабирования диапазона в [0, 1].
from sklearn.preprocessing import MinMaxScaler
data_nor = pd.DataFrame(MinMaxScaler().fit_transform(data)).assign(label=data.index) # normalized the data with min-max scaling
data_nor.columns = data.columns.to_list() + ["Date"]
data_nor.set_index("Date", inplace=True)
data_nor.tail(5)
Нахождение корреляции между переменными
Ещё одним необходимым шагом, прежде чем мы начнём обучать модель с использованием этих данных, является проверка корреляции между нашими целевыми переменными и переменными-предикторами. Мы будем использовать коэффициент корреляции Пирсона для анализа корреляции и, основываясь на следующем рисунке, удалим переменные слабой корреляции (коэффициент корреляции от -0,2 до 0,2).
data_nor.corr(method="pearson").style.background_gradient(cmap="coolwarm", axis=None).set_precision(2)
cor = data_nor.corr(method="pearson")
data_nor.drop(data_nor.columns[(cor.Index >= -0.2) & (cor.Index <= 0.2)], axis=1, inplace=True) # remove the weak corellation (% between -0.2 to 0.2)
data_nor.tail(5)
Прогнозирование временных рядов
Наборы для обучения и тестирования
В машинном обучении случайное разделение потока данных / теста является нормальным, потому что нет зависимости от одного наблюдения к другому. Тем не менее, это не относится к данным временных рядов. Как мы упоминали ранее, наши модели прогнозирования основаны на авторегрессионном анализе, что означает, что данные временного ряда 𝑌(𝑡+ℎ)Y(t+h)
коррелируют с его историческим значением 𝑌(𝑡)Y(t)
. Здесь мы захотим использовать значения в конце набора данных для тестирования, а всё остальное – для обучения.
Исходя из этого предположения, у нас было 1203 записи с ежедневными интервалами (почти 4 года); хорошим подходом было бы сохранить первые 1143 записи (3,1 года) для обучения и последние 60 записей (2 месяца) для тестирования.
train_size = int(len(df) * 0.9505)
X_train, y_train = pd.DataFrame(df.iloc[:train_size, :-1]), pd.DataFrame(df.iloc[:train_size, -1])
X_test, y_test = pd.DataFrame(df.iloc[train_size:, :-1]), pd.DataFrame(df.iloc[train_size:, -1])
Как Определить Параметры d? Стационарное обнаружение
Стационарность – это фактор, который описывает предсказуемость данных временных рядов. Строгий стационарный ряд описывает все распределение вероятностей как инвариантное по временному сдвигу, а слабый стационарный ряд сообщает, что среднее значение и ковариация инвариантны по временному сдвигу, что означает, что в момент t значения сильно зависит от его истории. Мы будем использовать функцию stationary
, чтобы проверить, являются ли данные временного ряда стационарными.
ori_df = y_train # original time series
fir_df = y_train.diff().dropna() # first difference time series
sec_df = y_train.diff().diff().dropna() # second difference time series
stationary_test = None
for i in range(3):
if i == 0:
print("Original Time Series")
stationary_test = adfuller(ori_df)
elif i == 1:
print("First Order Differencing")
stationary_test = adfuller(fir_df)
elif i == 2:
print("Second Order Differencing")
stationary_test = adfuller(sec_df)
print("ADF Statistic: %f" %stationary_test[0])
print("p-value: %f\n" %stationary_test[1])
Реализация модели SARIMAX
Функция модели SARIMAX аналогична модели ARIMA, но добавляет два других факторы: сезонность и внешние факторы.
Ключевым выводом является то, что SARIMAX требует не только аргументов p, d и q, которые требуются ARIMA, но также требует другой набор аргументов P, D и Q для аспекта сезонности, а также аргумента, называемого “m”. Это периодичность сезонного цикла данных; другими словами, это количество периодов в каждом сезоне.
При выборе значения m попытайтесь получить представление о том, когда сезонные данные изменяются. Если ваши точки данных разделяются на ежемесячной основе, а сезонный цикл составляет год, то установите m равным 12. Или, если точки данных разделяются на ежедневной основе, а сезонный цикл составляет неделю, то сделайте s равным 7. Вот таблица, на которую вы можете сослаться, чтобы настроить параметры m.
Дальше необходимо определить наилучшие параметры, которые соответствуют нашей модели. На этом шаге мы будем использовать один из самых мощных инструментов под названием auto_arima
, который поможет нам найти p, q, P и Q. Нам не нужно находить d с помощью этого инструмента, потому что мы уже выполнили стационарный тест в предыдущем разделе, что означает, что d и D можно установить равными 1. Не забудьте установить X_train в exogenous и seasonal в True.
from pmdarima import auto_arima
sarimax_param = auto_arima(y_train, exogenous=X_train, m=7, start_p=0, d=1, start_q=0, start_P=0, D=1, start_Q=0, max_p=3, max_q=1, max_P=3, max_Q=1, trace=True, seasonal=True)
Основываясь на результате, мы обнаружили, что наилучшие параметры для моделей SARIMAX – это когда p равно 1, q равно 0, P равно 3, Q равно 0. Затем мы можем поместить эти параметры в модель и начать обучать её. На этом этапе мы поместим обучающий набор данных в SARIMAX, а параметры, полученные из auto_arima, – в order, сезонные параметры – в seasonal_order.
from statsmodels.tsa.statespace.sarimax import SARIMAX
algorithm = SARIMAX(endog=y_train, exog=X_train, order=sarimax_param.get_params()["order"], seasonal_order=sarimax_param.get_params()["seasonal_order"])
model = algorithm.fit(disp=False)
Наконец, мы можем использовать обученную модель для прогнозирования данных тестирования. Здесь нам нужно установить начальные и конечные параметры в качестве длины тестовых данных и поместить все внешние факторы в параметр exog. Далее мы можем проверить частоту ошибок модели, чтобы убедиться в ее производительности.
from sklearn.metrics import mean_absolute_error, mean_squared_error
# forecast the data
forecast = model.get_prediction(start=len(y_train), end=len(y_train)+len(y_test)-1, exog=X_test, dynamic=True)
prediction = forecast.predicted_mean
ci = forecast.conf_int()
# check error rate
mse = mean_squared_error(y_test, prediction, squared=False)
rmse = mean_squared_error(y_test, prediction, squared=True)
print("The error rates of the SARIMAX forecasting are: \nMSE = %f \nRMSE = %f" %(mse, rmse))
Давайте сравним результаты прогнозирования с реальностью на графике:
plt.figure(figsize=(24, 9))
plt.plot(y_test.index, y_test, label="observation")
plt.plot(prediction.index, prediction, label="prediction")
plt.fill_between(ci.index, ci.iloc[:, 0], ci.iloc[:, 1], color="k", alpha=0.2)
plt.ylim([0.18, 0.3])
plt.title("SARIMAX Model Prediction", fontsize=21)
plt.legend()
plt.show()
Когда мы смотрим на график, мы можем сказать, что прогноз очень близок к фактическим движениям рынка, а также уловить случайность в прогнозировании, за исключением 11 ноября, когда FTX внезапно подала заявление о защите от банкротства.
Вот и все!
ПРЕДУПРЕЖДЕНИЕ: ЭТО РУКОВОДСТВО НЕ ЯВЛЯЕТСЯ ФИНАНСОВЫМ СОВЕТОМ
Информация, содержащаяся на этом веб-сайте, и ресурсы, доступные для загрузки через этот веб-сайт, не предназначены и не должны пониматься или истолковываться как финансовые рекомендации. Информация, содержащаяся на этом веб-сайте, не является заменой финансовой консультации от профессионала, который осведомлен о фактах и обстоятельствах вашей индивидуальной ситуации.