Riskplayer
Riskplayer личный блог
12 декабря 2024, 17:12

Случайный лес

    В этом опусе рассмотрим попытку использования алгоритм случайного леса для создания торгового модели для слива денег на примере индекса IMOEX. Используется язык питон и библиотеки pandas и scikit-learn. Модель будет предсказывать сторону закрытие на следующий день, т.е. оно положительное или отрицательное, и на основании этого строится торговая система.
df["Tomorrow"] = df["Close"].shift(-1)
df["Target"] = (df["Tomorrow"] > df["Close"]).astype(int)  # наша цель
    Очень важно, какие данные будут использоваться для прогнозирования. Здесь используется: показатель силы закрытия бара (т.е. (Close-Low)/(High-Low)) за текущий и предыдущий день, процентные соотношения между ценой закрытия и средними за периоды 2,10,15,25,50 дней по индексам IMOEX, RVI, RGBITR, и плюс цены закрытия индексов RVI, RGBITR.
    Для обучения модели используется период 2013-2022 гг., для проверки 2023-2024г.:
train = df.loc['2013':'2022']
test = df.loc['2023':]
    Для создания модели используется <a href=«scikit-learn. org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html»>RandomForestClassifier из библиотеки scikit-learn. Подгоняется только один параметр min_samples_split  (минимальное число объектов, необходимое для того, чтобы узел дерева мог бы расщепиться), по умолчанию этот параметр равен 2, но для того, чтобы модель была  не слишком переобученной, подгонка будет идти от 50 до 200 с шагом 10. Подгонка идет через кросс-валидацию TimeSeriesSplit с помощью процедуры GridSearchCV.
tscv = TimeSeriesSplit()
model = RandomForestClassifier(random_state = 0)
forest_params = {"min_samples_split": range(50,201,10)}
forest_grid = GridSearchCV(model, forest_params, n_jobs=-1, verbose = 1, cv = tscv)
forest_grid.fit(train[new_predictors], train['Target'])
   По итогу кросс-валидации получилось, что лучший параметр и лучший оценка:
best_params_ {'min_samples_split': 70}
best_score_ = 0.4986149584487535

   Теперь, проверяем на тестовом периоде полученную модель. Торговая система простая, если модель предсказывает, что закрытие завтра положительное, то встаем в лонг и на следующий день продаем, если наоборот, то в шорт, т.е. система всегда в рынке. Комиссия, проскальзывание и т.п. не учтены.
Случайный лес
    Вот такой «черный ящик» получился.

Полный код:
import pandas as pd

ticker = 'imoex'.upper()

filename = "..\\h5\\moex.h5"   # здесь хранятся минутки 
with pd.HDFStore(filename) as store:
    store = pd.HDFStore(filename)
    data_1min = store[ticker]
    rgbitr_1min = store['RGBITR']
    rvi_1min = store['RVI']
def convert_to_daily(intraday_data):
    return intraday_data.between_time('10:00', '18:40').resample('D').agg({'Open': 'first', 'High': 'max', 'Low': 'min', 'Close': 'last'}).dropna()
df = convert_to_daily(data_1min)
rgbitr = rgbitr_1min.between_time('10:00', '18:40').resample('D').agg({'Close': 'last'}).dropna()
rgbitr = rgbitr.rename(columns = {'Close' : 'rgbitr'})
rvi = rvi_1min.between_time('10:00', '18:40').resample('D').agg({'Close': 'last'}).dropna()
rvi = rvi.rename(columns = {'Close' : 'rvi'})
df = pd.concat([df, rgbitr, rvi], axis = 1)

df["Tomorrow"] = df["Close"].shift(-1)
df["Target"] = (df["Tomorrow"] > df["Close"]).astype(int)
df['ibs'] = (df['Close'] - df['Low'])/(df['High'] - df['Low'])
df['ibs1'] = df['ibs'].shift()
df['pct_chg'] = df['Close'].pct_change()
df = df.dropna()

new_predictors = ['ibs','ibs1', 'rgbitr', 'rvi' ]

horizons = [2,10,15,25,50]
for col in ['Close', 'rgbitr', 'rvi']:
    for horizon in horizons:
        rolling_averages = df.rolling(horizon).mean()
        ratio_column = f"{col}_Ratio_{horizon}"
        df.loc[:, ratio_column] = df[col] / rolling_averages[col] - 1
        new_predictors += [ratio_column]

df = df.dropna()
train = df.loc['2013':'2022']
test = df.loc['2023':]

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV, cross_val_score
from sklearn.model_selection import TimeSeriesSplit

tscv = TimeSeriesSplit()
model = RandomForestClassifier(random_state = 0)
forest_params = {"min_samples_split": range(50,201,10)}
forest_grid = GridSearchCV(model, forest_params, n_jobs=-1, verbose = 1, cv = tscv)
forest_grid.fit(train[new_predictors], train['Target'])

print(f"best_params_ {forest_grid.best_params_}")
print(f"best_score_ = {forest_grid.best_score_}")

test_preds = forest_grid.predict(test[new_predictors])
test = test.assign( preds = test_preds)
test['pos'] = test['preds']
test['pos'] = test['pos'].replace(0, -1)
test['ret'] = test['pos'].shift()*test['pct_chg']

import matplotlib.pyplot as plt
fig, axs = plt.subplots(1,2,figsize=(12, 4))
axs[0].axis([0, 10, 0, 10])
axs[0].text(1, 9, f"{ticker}")
axs[0].text(1, 7, f"min_samples_split = {forest_grid.best_params_['min_samples_split']}")
test['ret'].cumsum().plot(ax=axs[1])
axs[1].grid()
axs[1].set_title(f"test")


19 Комментариев
  • Петрович
    12 декабря 2024, 17:32
  • Фёдор Г.
    12 декабря 2024, 19:06
    А что такое best_score, это доля правильных ответов? Получается, модель угадывает 50 процентов правильно?
  • Replikant_mih
    12 декабря 2024, 21:50

    Не густо фичей).

     

    А как выглядит график зависимости метрики качества от min_samples_split?

Активные форумы
Что сейчас обсуждают

Старый дизайн
Старый
дизайн