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

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

Установка

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

Процесс установки достаточно подробно документирован на сайте FinRL. Отмечу, что в процессе сборки FinRL часто возникают ошибки из-за несогласованности пакетов при их обновлении. 

Установку следует производить в отдельно созданное виртуальное окружение с версией Python не ниже 3.8.

## install finrl library
!pip install git+https://github.com/AI4Finance-Foundation/FinRL.git

Подключаем библиотеки для работы

Подключим необходимые для дальнейшей работы библиотеки

import pandas as pd
import numpy as np
import yfinance as yf

from finrl.meta.preprocessor.yahoodownloader import YahooDownloader
from finrl.meta.preprocessor.preprocessors import FeatureEngineer, data_split
from finrl import config_tickers
from finrl.config import INDICATORS
from finrl.meta.env_stock_trading.env_stocktrading import StockTradingEnv
from finrl.agents.stablebaselines3.models import DRLAgent
from stable_baselines3.common.logger import configure
from finrl import config_tickers
from finrl.main import check_and_make_directories
from finrl.config import INDICATORS, TRAINED_MODEL_DIR, RESULTS_DIR

from pypfopt.efficient_frontier import EfficientFrontier

import matplotlib.pyplot as plt

import itertools

Загрузка данных для моделирования

Для загрузки рыночных данных мы воспользуемся библиотекой YahooFinance как одной из самых распространенных библиотек для Python в этой области. 

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

yf.download(tickers = "aapl", start='2020-01-01', end='2020-01-31')

Этот код загрузить данные о торгах акциями “Apple” с 1 по 31 января 2020 года. 

Выгружать данные по каждой бумаге довольно утомительно, поэтому в FinRL предусмотрен специальный инструмент загрузки данных:

YahooDownloader(start_date = '2020-01-01',
                end_date = '2020-01-31',
                ticker_list = ['aapl']).fetch_data()

Список бумаг, которые нужны для работы можно сформировать отдельно в виде простого списка или воспользоваться специально сформированным стандартным инструментом из FinRL: config_tickers

from finrl import config_tickers

config_tickers включает следующие списки:

[DOW_30_TICKER, 
 NAS_100_TICKER, 
 SP_500_TICKER, 
 HSI_50_TICKER, 
 SSE_50_TICKER, 
 CSI_300_TICKER, 
 CAC_40_TICKER, 
 DAX_30_TICKER, 
 TECDAX_TICKER, 
 MDAX_50_TICKER, 
 SDAX_50_TICKER, 
 LQ45_TICKER, 
 SRI_KEHATI_TICKER, 
 FX_TICKER]

Получить данные можно сразу по целому списку, просто указав его в переменной ticker_list:

YahooDownloader(start_date = '2020-01-01',
                end_date = '2020-01-31',
                ticker_list = config_tickers.DOW_30_TICKER).fetch_data()

Для нашего эксперимента зафиксируем начальную и конечную дату для тренировочной и тестовой выборки и загрузим данные:

TRAIN_START_DATE = '2009-01-01'
TRAIN_END_DATE = '2020-07-01'
TRADE_START_DATE = '2020-07-01'
TRADE_END_DATE = '2021-10-29'
df_raw = YahooDownloader(start_date = TRAIN_START_DATE,
                         end_date = TRADE_END_DATE,
                         ticker_list = config_tickers.DOW_30_TICKER).fetch_data()

Предобработка данных

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

Одним из самых примитивных подходов в этой области является прогнозирование на основе технических индикаторов.

FinRL в своем составе имеет список основных технических индикаторов и модуль для автоматизации процесса Feature engineering’a:

from finrl.config import INDICATORS
from finrl.meta.preprocessor.preprocessors import FeatureEngineer

fe = FeatureEngineer(use_technical_indicator=True,
                     tech_indicator_list = INDICATORS,
                     use_vix=True,
                     use_turbulence=True,
                     user_defined_feature = False)
processed = fe.preprocess_data(df_raw)

В состав списка индикаторов по умолчанию входят 8 элементов:

INDICATORS = ["macd", "boll_ub", "boll_lb", "rsi_30", "cci_30", "dx_30", "close_30_sma", "close_60_sma",]

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

В завершении объединим все данные в общий датасет:

list_ticker = processed["tic"].unique().tolist()
list_date = list(pd.date_range(processed['date'].min(),processed['date'].max()).astype(str))
combination = list(itertools.product(list_date,list_ticker))

processed_full = pd.DataFrame(combination,columns=["date","tic"]).merge(processed,on=["date","tic"],how="left")
processed_full = processed_full[processed_full['date'].isin(processed['date'])]
processed_full = processed_full.sort_values(['date','tic'])

processed_full = processed_full.fillna(0)

Разобьем его на тренировочный и торговый (тестовый):

train = data_split(processed_full, TRAIN_START_DATE,TRAIN_END_DATE)
trade = data_split(processed_full, TRADE_START_DATE,TRADE_END_DATE)

Построение модели среды

Для построения модели среды библиотека FinRL предоставляет стандартный конструктор, который обеспечивает создание модели среды и необходимых интерфейсов для взаимодействия агента и модели среды:

e_train_gym = StockTradingEnv(df = train, **env_kwargs)

В минимальной конфигурации конструктор принимает на вход DataFrame с данными на основе которых будет построена модель рынка. Так же можно передать словарь с дополнительными параметрами.

В нашем случае передадим нашему конструктору следующие параметры:

stock_dimension = len(train.tic.unique())
state_space = 1 + 2stock_dimension + len(INDICATORS)stock_dimension
buy_cost_list = sell_cost_list = [0.001] * stock_dimension
num_stock_shares = [0] * stock_dimension

env_kwargs = {
    "hmax": 100,
    "initial_amount": 1000000,
    "num_stock_shares": num_stock_shares,
    "buy_cost_pct": buy_cost_list,
    "sell_cost_pct": sell_cost_list,
    "state_space": state_space,
    "stock_dim": stock_dimension,
    "tech_indicator_list": INDICATORS,
    "action_space": stock_dimension,
    "reward_scaling": 1e-4
}

Для взаимодействия с моделями нам необходимо создать векторную обертку для нашего окружения. Это можно сделать стандартной функцией класса StockTradingEnv:

env_train, _ = e_train_gym.get_sb_env()

Вторым возвращаемым параметром здесь является состояние среды, возвращаемое функцией env.reset().

Построение RL агента и его обучение

Для создания агента достаточно выполнить два шага:

  1. Создать объект агента;

  2. Указать ему какой именно алгоритм из библиотеки необходимо использовать.

agent = DRLAgent(env = env_train)
model_a2c = agent.get_model("a2c")

Данный код создаст нам RL агента и загрузит модель “A2C” - т.е actor-critic. 

Далее нам необходимо обучить нашего агента на созданном окружении. Для этого достаточно вызвать метод train_model передав ему загруженную модель и число эпизодов обучения.

trained_a2c = agent.train_model(model=model_a2c,
                                total_timesteps=50000)

Так же можно дополнительно указать параметры логирования результатов, например в формате tensorboard.

Для созданного нами простого окружения доступны следующие модели: ["a2c","ddpg","ppo","td3","sac"], что соответствует моделям “actor-critic”, “deep deterministic policy gradient”, “twin delayed deep deterministic policy gradient”, “soft actor-critic”. Более подробно об этих моделях вы можете прочитать здесь.

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

trained_a2c.save(TRAINED_MODEL_DIR + "/agent_a2c")

Тестирование

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

e_trade_gym = StockTradingEnv(df = trade, turbulence_threshold =70,
                              risk_indicator_col='vix', **env_kwargs)
env_trade, obs_trade = e_trade_gym.get_sb_env()

Получим предсказания нашего агента для тестовой среды:

df_account_value_a2c, df_actions_a2c = DRLAgent.DRL_prediction(model=trained_a2c,
                                                               environment = e_trade_gym)

Поскольку мы имеем дело не с одной бумагой, а с портфелем инвестиций, то для оценки эффективности действий нашего агента воспользуемся методом MVO (Mean-Variance Optimization) - широко применяемым в теории портфельного управления. Это стратегия, в рамках современной теории управления портфелем, созданной Гарри Марковицем, направлена на создание оптимального инвестиционного портфеля путем балансировки компромисса между ожидаемой доходностью и риском.

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

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

Сравнение обученного агента с базовыми торговыми стратегиями
Сравнение обученного агента с базовыми торговыми стратегиями

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

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

В следующих статьях цикла мы рассмотрим пути улучшения торговых стратегий и построения более осмысленных агентов с помощью библиотеки FinRL.

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

Комментарии (10)


  1. alexeyk500
    06.04.2024 16:45
    +3

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


    1. SGarik Автор
      06.04.2024 16:45
      +1

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


      1. sunnybear
        06.04.2024 16:45
        +3

        Пару (сотен) статей. Пока ни одна известная мне модель обучения / прогнозирования рынка ценных бумаг не давала эффекта на длинной дистанции. Но все возможно!


        1. charypopper
          06.04.2024 16:45

          Мартингейл и печатный станок - правда места заняты


          1. sunnybear
            06.04.2024 16:45

            есть еще сетка и арбитраж, но это все не модели прогнозирования


    1. santiment
      06.04.2024 16:45

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


  1. avshkol
    06.04.2024 16:45

    Робот имеет доступ только к объёмам и ценам торгов и не видит информацию по изменениям баланса, новостям о компании, объявленным дивидендам, верно?


  1. Kahelman
    06.04.2024 16:45

    В качестве базы для сравнения надо брать индекс, например Dow Jones (смотря на каком рынке тренируете) и если ваша стратегия не генерирует стабильный результат выше индекса, то можно даже не заморачиваться.

    Подсказка: на длительном интервале вы не можете достичь доходность выше индекса. :)

    Балыки этот спор уже выиграл :)


  1. Lexa83
    06.04.2024 16:45

    Так а где собственно performance statistics - beta, alpha, sharpe, max. drawdown хотя бы?

    График equity увы ничего не скажет о стратегии.


  1. qiper
    06.04.2024 16:45

    Мне кажется, что спекулятивные рынки мучают нейросетями все кому не лень лет тридцать уже)