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")


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

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

     

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

    • Replikant_mih
      12 декабря 2024, 22:32
      Riskplayer, Ну и вообще диапазон сам от 0.486 до 0.498. Не знаю, что это за метрика — видимо что-то нормированное от 0 до 1. И волатильность этого графика небольшая относительно 1. Короче. любое значение можно брать по оси Х). А, ну и когда метрика слабо зависит от параметра для меня это если если не прям признак, то точно повод задуматься, что это мусорный параметр.
  • Igor K
    13 декабря 2024, 11:41
    Код писали с нуля или брали у Чана? У него в книжке матлаб, но вроде бы он на своем сайте выложил питоновский код.
  • wrmngr
    13 декабря 2024, 21:34
    Ох уж эти ML щики! Ни контроля на tradeable, ни учёта костов, ни анализа предметной области. Лишь бы зафитить что не попадя!
      • wrmngr
        14 декабря 2024, 00:04
        Riskplayer, предмет его применимости относительно трейдинга простой: убедить инвестора или работодателя выделить бюджет на рисерч. А потом сидим тихо и фитим модельки под котировки. Никому не нужные

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

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