Ускорение обучения моделей и улучшение прогнозов фондового рынка

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

Что такое генетические алгоритмы?

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

Как работают генетические алгоритмы?

Генетические алгоритмы начинаются с исходной популяции потенциальных решений данной проблемы. Каждое решение представлено в виде набора параметров или переменных, которые часто кодируются в виде строк двоичных цифр (0 и 1). Затем эти решения оцениваются и им присваивается оценка пригодности на основе их эффективности в решении проблемы.

Изображение с www.pico.net

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

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

Пример из реальной жизни: проблема коммивояжера

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

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

Изображение с сайта www.linkedin.com

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

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

Генетические алгоритмы обновления нейронных сетей

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

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

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

Интеграция малых нейронных сетей и генетических алгоритмов

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

Изображение с fisher.osu.edu

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

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

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

Обновление сети: генетический алгоритм против регулярного подхода

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

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

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

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

Изображение с gcc.edu

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

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

Осуществление Среда «Продавать-Держать-Покупать»

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

Теперь мы продвинемся вперед и реализуем среду «продажа-удержание-покупка», которая будет использоваться генетическим алгоритмом, используя 1-часовой файл данных акций OIH.

Давайте посмотрим на пошаговую реализацию среды Sell-Hold-Buy Environment.

Шаг 1: Импорт библиотек

Первым шагом в реализации среды Sell-Hold-Buy является импорт необходимых библиотек. Мы импортируем numpy для численных вычислений, тренажерный зал для создания среды и spaces из gym для определения пространств наблюдения и действий. gym

import numpy as np
import gym
from gym import spaces

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

Далее мы определяем константы и создаем класс SellHoldBuyEnv, который является подклассом gym.Env. Внутри класса мы определяем конструктор (__init__), который инициализирует среду необходимыми данными, пробелами и переменными.

# Operations
SELL = 0
HOLD = 1
BUY = 2

class SellHoldBuyEnv(gym.Env):

def __init__(self, observation_size, features, closes):
# Data
self.__features = features
self.__prices = closes

# Spaces
self.observation_space = spaces.Box(low=np.NINF, high=np.PINF, shape=(observation_size,), dtype=np.float32)
self.action_space = spaces.Discrete(3)

# 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

Шаг 3: Определение метода сброса

Метод 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()

Шаг 4: Определение метода шага

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

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

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.__current_action = BUY
elif self.__current_action == BUY and action == SELL:
step_reward = self.__prices[self.__current_tick] - self.__open_price
self.__current_profit += step_reward
self.__current_action = HOLD

if step_reward > 0:
self.__wins += 1
else:
self.__losses += 1

# 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

Шаг 5: Определение метода наблюдения Get

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

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

# Generate a copy of the observation to avoid changing the original data
obs = self.__features[(self.__current_tick - self.__start_tick):self.__current_tick]

# Return the calculated observation
return obs

Следуя этим пяти шагам, мы успешно внедрили среду «Продажа-Удержание-Покупка». Среда позволяет агенту взаимодействовать с симуляцией торговли акциями, предпринимать действия, получать вознаграждения и наблюдать за состоянием среды.

Использование среды «Продавать-Держать-Покупать» для обучения и прогнозирования того, когда покупать и продавать актив

В этом разделе мы предоставим пошаговое руководство по реализации настройки и выполнения нашей модели для обучения и прогнозирования. Мы будем использовать Python, PyGAD и Keras для создания и обновления нейронной сети с помощью генетического алгоритма. Мы также предоставим фрагменты кода для каждого шага, чтобы облегчить процесс реализации. Как упоминалось ранее, модель будет обучена на 1-часовых данных фондового рынка OIH, чтобы предсказать, когда покупать и продавать актив, и мы покажем, как оценить производительность модели на тестовом наборе данных.

Шаг 1: Импорт библиотек и SellHoldBuyEnv

На этом шаге мы импортируем необходимые библиотеки, включая numpypandaspandas_ta, pygad и pygad.kerasgapygad Кроме того, мы импортируем Sequential модель и слой Dense из tensorflow.keras.models Наконец, мы импортируем среду SellHoldBuyEnv, которую мы реализовали ранее.

import numpy as np
import pandas as pd
import pandas_ta as ta
import pygad
import pygad.kerasga

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense

from sell_hold_buy_env import SellHoldBuyEnv

Шаг 2: Определите константы и загрузите данные

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

Затем мы загружаем данные из CSV-файла с помощью pd.read_csv, применяем индикатор полос Боллинджера с pandas_ta и вычисляем процент внутри полос и волатильность между верхней и нижней полосами для отправки в модель в качестве наблюдения.
Затем мы разделили данные на наборы данных для обучения и тестирования на основе даты.

# Constants
OBS_SIZE = 32
FEATURES = 2
SOLUTIONS = 20
GENERATIONS = 50

# Loading data and split into train and test datasets
df = pd.read_csv('OIH_1H.csv.gz', compression='gzip')
df.ta.bbands(close=df['close'], length=20, append=True)
df = df.dropna()
pd.options.mode.chained_assignment = None
df['high_limit'] = df['BBU_20_2.0'] + (df['BBU_20_2.0'] - df['BBL_20_2.0']) / 2
df['low_limit'] = df['BBL_20_2.0'] - (df['BBU_20_2.0'] - df['BBL_20_2.0']) / 2
df['close_percentage'] = np.clip((df['close'] - df['low_limit']) / (df['high_limit'] - df['low_limit']), 0, 1)
df['volatility'] = df['BBU_20_2.0'] / df['BBL_20_2.0'] - 1
train = df[df['date'] < '2022-01-01']
test = df[df['date'] >= '2022-01-01']

Шаг 3: Определите функцию фитнеса

На этом шаге мы определим функцию приспособленности (fitness_func), которая будет использоваться экземпляром PyGAD. Эта функция принимает решение и индекс решения в качестве входных данных.

Внутри функции фитнеса мы устанавливаем веса модели на веса решения с помощью pygad.kerasga.model_weights_as_matrix и model.set_weights. Затем мы запускаем прогнозы на основе данных поезда с использованием среды, вычисляем общее вознаграждение и соответствующим образом обновляем total_reward.

def fitness_func(solution, sol_idx):
global model, observation_space_size, env

# Set the weights to the model
model_weights_matrix = pygad.kerasga.model_weights_as_matrix(model=model, weights_vector=solution)
model.set_weights(weights=model_weights_matrix)

# Run a prediction over the train data
observation = env.reset()
total_reward = 0

done = False
while not done:
state = np.reshape(observation, [1, observation_space_size])
q_values = predict(state, model_weights_matrix)
action = np.argmax(q_values[0])
observation, reward, done, info = env.step(action)
total_reward += reward

# Print the reward and profit
print(f"Solution {sol_idx:3d} - Total Reward: {total_reward:10.2f} - Profit: {info['current_profit']:10.3f}")

if sol_idx == (SOLUTIONS-1):
print("".center(60, "*"))

# Return the solution reward
return total_reward

Шаг 4: Определите функцию прогнозирования

На этом шаге мы определяем функцию прогнозирования (потому что функция model.predict имеет тенденцию быть очень медленной), которая используется для predict с помощью модели. Эта функция принимает входные данные X и веса W в качестве входных данных.

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

def predict(X, W):
X = X.reshape((X.shape[0], -1)) # Flatten
X = X @ W[0] + W[1] # Dense
X[X < 0] = 0 # Relu
X = X @ W[2] + W[3] # Dense
X[X < 0] = 0 # Relu
X = X @ W[4] + W[5] # Dense
X = np.exp(X) / np.exp(X).sum(1)[..., None] # Softmax
return X

Шаг 5: Создайте среду обучения и постройте модель

Здесь мы создаем экземпляр среды SellHoldBuyEnv, используя данные поезда, размер наблюдения и функции. Также вычисляется размер пространства наблюдения и размер пространства действия.

Затем мы строим модель нейронной сети, используя Sequential и Dense из tensorflow.keras.models Архитектура модели состоит из двух скрытых слоев по 16 блоков каждый и линейного выходного слоя.

# Create a train environment
env = SellHoldBuyEnv(observation_size=OBS_SIZE, features=train[['close_percentage','volatility']].values, closes=train['close'].values)
observation_space_size = env.observation_space.shape[0] * FEATURES
action_space_size = env.action_space.n

# Create Model
model = Sequential()
model.add(Dense(16, input_shape=(observation_space_size,), activation='relu'))
model.add(Dense(16, activation='relu'))
model.add(Dense(action_space_size, activation='linear'))
model.summary()

Шаг 6: Создайте экземпляр генетического алгоритма

На этом шаге мы создадим экземпляр pygad.kerasga.KerasGA используя модель и количество решений (SOLUTIONS Затем мы создаем экземпляр pygad.GA, используя указанное количество поколений (GENERATIONS), количество спаривания родителей, начальную популяцию, функцию приспособленности и другие параметры.

# Create Genetic Algorithm
keras_ga = pygad.kerasga.KerasGA(model=model, num_solutions=SOLUTIONS)

ga_instance = pygad.GA(num_generations=GENERATIONS,
num_parents_mating=5,
initial_population=keras_ga.population_weights,
fitness_func=fitness_func,
parent_selection_type="sss",
crossover_type="single_point",
mutation_type="random",
mutation_percent_genes=10,
keep_parents=-1)

Шаг 7: Запустите генетический алгоритм

На этом шаге мы запускаем генетический алгоритм с помощью run() ga_instance.

# Run the Genetic Algorithm
ga_instance.run()

Шаг 8: Получите лучшее решение и установите вес для модели

После того, как генетический алгоритм завершит работу, мы получим лучшее решение, его значение пригодности и индекс с помощью best_solution(). Затем мы распечатываем значение пригодности и индекс наилучшего решения.

# Show details of the best solution.
solution, solution_fitness, solution_idx = ga_instance.best_solution()
print(f"Fitness value of the best solution = {solution_fitness}")
print(f"Index of the best solution: {solution_idx}")

Шаг 9: Создайте тестовую среду и установите весовые коэффициенты для наилучшего решения

Здесь мы создаем экземпляр SellHoldBuyEnv, используя тестовые данные, размер наблюдения и функции. Затем мы устанавливаем веса наилучшего решения для модели, используя pygad.kerasga.model_weights_as_matrix и model.set_weights.

# Create a test environment
env = SellHoldBuyEnv(observation_size=OBS_SIZE, features=test[['close_percentage','volatility']].values, closes=test['close'].values)

# Set the weights of the best solution to the model
best_weights_matrix = pygad.kerasga.model_weights_as_matrix(model=model, weights_vector=solution)
model.set_weights(weights=best_weights_matrix)

Шаг 10: Выполнение прогнозов на тестовых данных

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

# Run predictions over the test data
observation = env.reset()
total_reward = 0

done = False
while not done:
state = np.reshape(observation, [1, observation_space_size])
q_values = predict(state, best_weights_matrix)
action = np.argmax(q_values[0])
observation, reward, done, info = env.step(action)
total_reward += reward

Шаг 11: Распечатайте окончательные результаты

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

# Show the final result
print(' RESULT '.center(60, '*'))
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_infoданных.

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

************************** RESULT **************************
* Profit/Loss: 27.920
* Wins: 105 - Losses: 82
* Win Rate: 56.15%

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

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

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

Источник