Блог им. Riskplayer

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

    В этом опусе рассмотрим попытку использования алгоритм случайного леса для создания торгового модели для слива денег на примере индекса 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")


★2
8 комментариев
А что такое best_score, это доля правильных ответов? Получается, модель угадывает 50 процентов правильно?
Да, примерно так.
best_score — лучшая средняя оценка после кросс-валидации.
Хотя, как ни странно, на тестовом периоде оценка будет 0,617.
avatar

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

 

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


Выглядит он, скажем прямо, так себе. 
avatar
Riskplayer, Ну и вообще диапазон сам от 0.486 до 0.498. Не знаю, что это за метрика — видимо что-то нормированное от 0 до 1. И волатильность этого графика небольшая относительно 1. Короче. любое значение можно брать по оси Х). А, ну и когда метрика слабо зависит от параметра для меня это если если не прям признак, то точно повод задуматься, что это мусорный параметр.
Код писали с нуля или брали у Чана? У него в книжке матлаб, но вроде бы он на своем сайте выложил питоновский код.
avatar
Книгу Чана читал, но в матлабе пока не проверял и его код на питоне не видел.
Код, в основном, сам писал.
avatar

теги блога Riskplayer

....все тэги



UPDONW
Новый дизайн