Обучение с подкреплением стало мощным методом разработки автоматизированных торговых стратегий. Использование принципов проб и ошибок позволяет алгоритмам изучать оптимальные политики принятия решений на основе наблюдаемой среды. В этой статье мы углубимся в обучение с подкреплением применительно к стратегиям торговли акциями, изучая альтернативный подход, использующий MaskablePPO (оптимизация проксимальной политики) и MultiInputPolicy. Кроме того, мы представим Процентный осциллятор цен (PPO) как средство для получения наблюдений для нашей торговой модели.

Иллюстрированное обучение с подкреплением — Акробатический дельфин

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

В этом сценарии дельфин играет роль «агента», а дрессировщик — «окружающей среды». Агент выполняет действия (например, плавает или прыгает), а среда обеспечивает обратную связь на основе результатов этих действий (например, поощряет успешные прыжки и наказывает за неудачи). Со временем дельфин учится оптимизировать свои действия, максимизируя совокупное вознаграждение, постепенно совершенствуя свою стратегию, пока не овладеет акробатикой.

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

Представляем MaskablePPO — другой подход

В нашем исследовании обучения с подкреплением для стратегий торговли акциями мы будем использовать альтернативный подход, который использует MaskablePPO. Хотя обычно используются популярные алгоритмы обучения с подкреплением, такие как Deep Q-Network (DQN) и Proximal Policy Optimization (PPO), MaskablePPO предлагает явные преимущества в определенных сценариях.

MaskablePPO — это расширение PPO, алгоритма оптимизации политик, целью которого является максимизация вознаграждения при обеспечении стабильности обучения. «Маскируемый» аспект MaskablePPO относится к его способности выборочно применять ограничения к различным компонентам политики во время обучения. Таким образом, он позволяет гибко настраивать компромисс между разведкой и эксплуатацией, обеспечивая точную настройку поведения модели.

Далее мы представим концепцию MultiInputPolicy, которая играет решающую роль в реализации нашей торговой стратегии.

Политика MultiInputPolicy и создание словаря с последней операцией и наблюдением

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

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

Использование процентного ценового осциллятора для наблюдений

В нашем подходе мы будем использовать процентный осциллятор цен (PPO), версию индикатора MACD с процентным изменением, чтобы генерировать наблюдения для нашей торговой модели. PPO — это технический индикатор, обычно используемый в финансовом анализе для измерения импульса и определения потенциальных разворотов тренда.

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

TL;DR здесь — это полный исходный код, если вы опытный разработчик и разбираетесь в обучении с подкреплением.

Реализация среды Stop-Loss-Target

В статье Проверка стратегий торговли акциями с использованием Python (подготовка данных) мы узнали, как получить данные об акциях OIH из Интернета и как создать файл, содержащий данные за разные временные интервалы.



Теперь мы двинемся вперед и реализуем среду Stop-Loss-Target для обучения с подкреплением, используя 15-минутный файл данных акций OIH.

Импорт библиотек

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

import numpy as np
import gym
from gym import spaces

Определение констант, класса среды и конструктора класса

На этом шаге мы определяем константы и создаем класс среды с его конструктором. Константы представляют штрафы, точки выхода и операции. Класс среды с именем StopLossTargetEnv является подклассом gym.Env.

# Penalization
NOOP_PENALIZATION = 0.01

# Exit Points
STOP_LOSS = 0.01
TARGET = 0.015

# Operations
HOLD = 0
BUY = 1

class StopLossTargetEnv(gym.Env):

    def __init__(self, observation_size, features, prices):

        # Data
        self.__features = features
        self.__prices = prices

        # Spaces 
        self.observation_space = spaces.Dict({
            'last_action': spaces.Discrete(2), 
            'observation': spaces.Box(low=np.NINF, high=np.PINF, shape=(observation_size, features.shape[1]), dtype=np.float32)})
        self.action_space = spaces.Discrete(2)

        # Episode Management
        self.__start_tick = observation_size
        self.__end_tick = len(self.__prices)
        self.__current_tick = self.__end_tick

        # Position Management
        self.__current_action = HOLD
        self.__current_profit = 0
        self.__wins = 0
        self.__losses = 0

Определение метода сброса

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

def reset(self):

    # Reset the current action and current profit
    self.__current_action = HOLD
    self.__current_profit = 0
    self.__wins = 0
    self.__losses = 0

    # Reset the current tick pointer and return a new observation
    self.__current_tick = self.__start_tick
    return self.__get_observation()

Определение пошагового метода

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

def step(self, action):

    # If current tick is over the last index in the feature array, the environment needs to be reset
    if self.__current_tick > self.__end_tick:
        raise Exception('The environment needs to be reset.')

    # Compute the step reward (Penalize the agent if it is stuck doing anything)
    step_reward = 0
    if self.__current_action == HOLD and action == BUY:
        self.__open_price = self.__prices[self.__current_tick]
        self.__stop_loss = self.__open_price * (1 - STOP_LOSS)
        self.__target = self.__open_price * (1 + TARGET)
        
        self.__current_action = BUY
    elif self.__current_action == BUY:
        current_price = self.__prices[self.__current_tick]
        
        if current_price < self.__stop_loss or current_price > self.__target:
            step_reward = current_price - self.__open_price
            self.__current_profit += step_reward
            self.__current_action = HOLD
            
            if step_reward > 0:
                self.__wins += 1
            else:
                self.__losses += 1
    elif self.__current_action == HOLD:
        step_reward = -NOOP_PENALIZATION

    # Generate the custom info array with the real and predicted values
    info = {
        'current_action': self.__current_action,
        'current_profit': self.__current_profit,
        'wins': self.__wins,
        'losses': self.__losses
    }

    # Increase the current tick pointer, check if the environment is fully processed, and get a new observation
    self.__current_tick += 1
    done = self.__current_tick >= self.__end_tick
    obs = self.__get_observation()

    # Returns the observation, the step reward, the status of the environment, and the custom information
    return obs, step_reward, done, info

Определение метода action_masks

Метод action_masks используется MaskablePPO для фильтрации доступных действий. В этом случае мы разрешаем действие «КУПИТЬ» только в том случае, если текущая позиция «ДЕРЖАТЬ». Он возвращает массив масок, где True представляет разрешенные действия.

def action_masks(self):

    # Allow to BUY only if the current position is HOLD
    mask = np.ones(self.action_space.n, dtype=bool)
    mask[BUY] = self.__current_action == HOLD

    return mask

Определение метода получения наблюдения

Метод __get_observation — это частный вспомогательный метод, который возвращает текущее наблюдение. Он проверяет, выходит ли текущий тик за пределы последнего значения в массиве признаков, и возвращает None, если это так. В противном случае он возвращает рассчитанное наблюдение, которое включает в себя последнее действие и фрагмент массива признаков.

def __get_observation(self):

    # If the current tick is over the last value in the feature array, the environment needs to be reset
    if self.__current_tick >= self.__end_tick:
        return None

    # Return the calculated observation
    return {
        'last_action': self.__current_action,
        'observation': self.__features[(self.__current_tick - self.__start_tick):self.__current_tick]
    }

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

Использование среды Stop-Loss-Target для обучения и прогнозирования момента покупки и продажи актива

В этом разделе мы проведем вас через процесс настройки и запуска нашей модели для обучения и прогнозирования. Мы будем использовать Python и библиотеку Stable Baselines3 (версия ‹ 2.0) вместе с тренажерным залом OpenAI (не тренажерным залом) для запуска модели обучения с подкреплением. В частности, мы будем использовать алгоритм Maskable Proximal Policy Optimization (MaskablePPO). Чтобы упростить задачу, мы добавим фрагменты кода для каждого шага, которые помогут вам эффективно реализовать модель. Наша цель — обучить модель, используя 15-минутные данные фондового рынка OIH, и использовать ее для прогнозирования оптимальных точек покупки и продажи. Кроме того, мы покажем, как оценить производительность модели с помощью тестового набора данных.

Чтение данных и подготовка набора данных

Сначала мы считываем данные из CSV-файла и предварительно обрабатываем их для создания процентного ценового осциллятора (PPO). Мы разделяем данные на обучающие и тестовые наборы данных.

import math
import numpy as np
import pandas as pd
import pandas_ta as ta

from sb3_contrib import MaskablePPO
from stable_baselines3.common.env_util import make_vec_env

from stoploss_target_env import StopLossTargetEnv

# Read the data, generate the Percentage Price Oscillator (PPO) and create the train and test dataset
df = pd.read_csv('OIH_15T.csv.gz', compression='gzip')
df.ta.ppo(close=df['close'], append=True)
df.dropna(inplace=True)
train = df[(df['date'] >= '2018-01-01') & (df['date'] <= '2022-01-01')]
test = df[df['date'] > '2022-01-01']

Создание среды обучения

Затем мы создаем среду обучения, используя функцию make_vec_env из Stable Baselines. Мы передаем класс StopLossTargetEnv в качестве среды для создания экземпляра вместе с необходимыми параметрами, такими как размер наблюдения, функции и цены.

# Create 4 parallel train environments
env = make_vec_env(StopLossTargetEnv, 
    seed=42, n_envs=4, 
    env_kwargs={'observation_size': 3, 'features': train[['PPOStopLossTargetEnv26_9','PPOhStopLossTargetEnv26_9','PPOsStopLossTargetEnv26_9']].values, 'prices': train['close'].values})

Обучение модели

Мы инициализируем модель MaskablePPO, передавая «MultiInputPolicy» в качестве политики для обработки нескольких входных данных, среды обучения и устанавливаем уровень детализации на 1. Затем мы обучаем модель для заданного общего количества шагов по времени.

# Train the model
model = MaskablePPO("MultiInputPolicy", env, verbose=1)
model.learn(total_timesteps=10_000_000)

Сохранение и загрузка модели

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

# Save, remove, and reload the model (To be sure it works as expected)
model.save("maskableppo_stoploss_target")
del model
model = MaskablePPO.load("maskableppo_stoploss_target")

Создание тестовой среды и прогнозирование

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

# Create a test environment
env = StopLossTargetEnv(observation_size=3, features=test[['PPOStopLossTargetEnv26_9','PPOhStopLossTargetEnv26_9','PPOsStopLossTargetEnv26_9']].values, prices=test['close'].values)

# Create the required variables for calculation
done = False

# Predict the test values with the trained model
obs = env.reset()
while not done:
    action, _states = model.predict(obs, deterministic=True)
    obs, rewards, done, info = env.step(action)
    
    print(f"Action: {info['current_action']} - Profit: {info['current_profit']:6.3f}")

Отображение результатов

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

# Show results
print(' RESULT '.center(56, '*'))
print(f"* Profit/Loss: {info['current_profit']:6.3f}")
print(f"* Wins: {info['wins']} - Losses: {info['losses']}")
print(f"* Win Rate: {100 * (info['wins']/(info['wins'] + info['losses'])):6.2f}%")

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

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

Вот результат прибыли/убытка после исполнения

************************ RESULT ************************
* Profit/Loss: 62.554
* Wins: 29 - Losses: 22
* Win Rate:  56.86%

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

Заключение

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

Чтобы построить торговую среду, мы реализовали класс StopLossTargetEnv, который следует подходу StopLoss/Target. Мы описали реализацию в пять шагов, предоставив фрагменты кода для каждого шага. Мы определили необходимые константы, класс окружения и конструктор класса. Мы также реализовали метод reset, метод step и метод action_masks, который фильтрует действия с помощью MaskablePPO. Кроме того, мы представили метод Get Observation, который использует подход MultiInputObservation для создания наблюдения.

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

В заключение, эта статья продемонстрировала, как обучение с подкреплением, в частности, с использованием алгоритма MaskablePPO и подхода StopLoss/Target, может применяться для разработки стратегий торговли акциями. Используя концепцию MultiInputPolicy и процентного ценового осциллятора (PPO), мы смогли создать надежную и адаптируемую торговую среду. Реализация и примеры, представленные в этой статье, предлагают отправную точку для дальнейших исследований и экспериментов в области алгоритмической торговли. Благодаря достижениям в обучении с подкреплением и доступности финансовых данных этот подход обещает разработку более эффективных и интеллектуальных торговых стратегий в будущем.

Если вам понравилась история, пожалуйста, не стесняйтесь потратить пару секунд, чтобы оставить комментарий и дать мне 10 хлопков. Это очень мотивирует меня продолжать писать. :)

Загрузите полный исходный код этой статьи здесь.

Twitter: https://twitter.com/diegodegese
LinkedIn: https://www.linkedin.com/in/ddegese
Github: https://github.com/crapher

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