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

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

Общие сведения об обратном тестировании

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

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

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

Акции, цены которых мы попытаемся предсказать, вырастут они или упадут, будут Google. Компания Google была основана 4 сентября 1998 года Ларри Пейджем и Сергеем Брином, когда они были аспирантами Стэнфордского университета в Калифорнии. Вместе они владеют около 14% его публичных акций и контролируют 56% голосов акционеров через акции с суперголосованием.

Мы будем использовать библиотеку python Yahoo Finance для загрузки данных. Yahoo Finance использует pandas библиотеку и автоматически помещает все данные в красивый фрейм данных. Мы используем период max для получения всех данных о ценах на акции Google.

!pip install yfinance --quiet
import yfinance as yf
data = yf.Ticker("GOOGL")
data_hist = data.history(period="max")
data_hist

Теперь, когда данные загружены, давайте посмотрим, как цифры изменились за эти годы.

for i in range(len(data_hist.columns[:-2])):
    plt.plot(data_hist[data_hist.columns[i]], color = color[i])
    plt.gca().spines['top'].set_visible(False)
    plt.gca().spines['right'].set_visible(False)
    plt.axhline(data_hist[data_hist.columns[i]].mean(), linestyle='--', lw=2, zorder=1, color='black')
    plt.title("Google "+data_hist.columns[i] + " figures", fontsize=18)
    plt.xlabel('Years')
    plt.ylabel(data_hist.columns[i])
    plt.legend([data_hist.columns[i]])
    plt.show()

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

В библиотеке pandas есть скользящий метод, который будет смотреть на количество строк в данных (в нашем случае 2), а затем мы говорим, что нужно вернуть 1-ю строку, если 2-я строка выше, иначе вернуть 0, и это дает нам нашу цель .

data_hist["Target"] = data_hist.rolling(2).apply(lambda x: x.iloc[1] > x.iloc[0])["Close"]

Теперь, когда у нас есть цель, мы должны сдвинуть наши данные на 1 строку.

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

df = data_hist.copy()
df = df.shift(1)
df

Давайте проверим количество значений в нашем столбце Target.

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

ros = RandomOverSampler(random_state=0)
X = df[["Open", "High", "Low", "Close", "Volume"]].values
y = df['Target'].values
X_resampled, y_resampled  = ros.fit_resample(X,y)

Мы начинаем с базовой модели с max_depth 3 и скоростью обучения 0,1, а остальные параметры по умолчанию.

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

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

X_train, X_test, y_train, y_test = train_test_split(X_resampled, y_resampled, test_size=0.25, random_state=42)
model = XGBClassifier(max_depth=3, learning_rate=0.1)
history = model.fit(X_train, y_train, early_stopping_rounds =2, eval_set =[(X_train, y_train), (X_test, y_test)], 
                    eval_metric=["error", "logloss"], verbose=0)
predictions = model.predict(X_test)
print(classification_report(y_test, predictions))

Посмотрим на производительность модели с помощью графиков

Мы видим, что происходит некоторое переоснащение, о котором мы позаботимся позже. Мы настроили нашу модель, изменив max_depth на 15 и параметр reg_lambda (регуляризация) на 0,6, и это привело нас к точности 71%.

Давайте проведем обратное тестирование и узнаем оценку модели.

def backtest(data, model, predictors, start=1000, step=50):
    predictions = []
    # Loop over the dataset in increments
    for i in range(start, data.shape[0], step):
        # Split into train and test sets
        train = data.iloc[0:i].copy()
        test = data.iloc[i:(i+step)].copy()
        
        # Fit the model
        model.fit(train[predictors], train["Target"])
        
        # Make predictions
        preds = model.predict_proba(test[predictors])[:,1]
        preds = pd.Series(preds, index=test.index)
        preds[preds > .6] = 1
        preds[preds<=.6] = 0
        
        # Combine predictions and test values
        combined = pd.concat({"Target": test["Target"],"Predictions": preds}, axis=1)
        
        predictions.append(combined)
    
    return pd.concat(predictions)

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

Делая прогнозы, мы используем нечто, известное как predict_proba, которое в основном дает нам вероятности. Модель обычно имеет 0,5 в качестве порога для классификации точек данных, но мы делаем шаг вперед до 0,6, а затем возвращаем эти прогнозы.

Используя приведенную выше функцию, мы получили показатель точности 67%. Одним из способов повышения производительности модели является разработка признаков, и именно этим мы и займемся. В приведенной ниже ячейке кода мы добавляем среднее значение за неделю, среднее значение за квартал и среднее значение за год.

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

Подробнее здесь о выборе функций

weekly_mean = df.rolling(7).mean()
quarterly_mean = df.rolling(90).mean()
annual_mean = df.rolling(365).mean()
weekly_trend = df.shift(1).rolling(7).mean()["Target"]
df["weekly_mean"] = weekly_mean["Close"] / df["Close"]
df["quarterly_mean"] = quarterly_mean["Close"] / df["Close"]
df["annual_mean"] = annual_mean["Close"] / df["Close"]

df["annual_weekly_mean"] = df["annual_mean"] / df["weekly_mean"]
df["annual_quarterly_mean"] = df["annual_mean"] / df["quarterly_mean"]
df["weekly_trend"] = weekly_trend

df["open_close_ratio"] = df["Open"] / df["Close"]
df["high_close_ratio"] = df["High"] / df["Close"]
df["low_close_ratio"] = df["Low"] / df["Close"]

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

predictors  = ['Open', 'High','Low','Close','Volume','weekly_mean','quarterly_mean','annual_mean',
 'annual_weekly_mean','annual_quarterly_mean','weekly_trend','open_close_ratio','high_close_ratio','low_close_ratio']

backtestpredictions = backtest(df, model, predictors)
print(classification_report(backtestpredictions['Target'], backtestpredictions['Predictions']))

И мы можем с уверенностью сказать, что модель достигла точности 85%, что означает колоссальный скачок на 14% просто за счет добавления предикторов, которые имеют смысл и дают модели больше информации.

Для получения полного кода нажмите здесь https://lnkd.in/dB6hhbCJ

ЗАКЛЮЧЕНИЕ:-

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

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