В этом уроке мы будем создавать и применять усиливающийся учебный прибор, чтобы научиться создавать биткойн-торговый бот. В этом уроке мы будем использовать OpenAI gym и PPO бот из stable-baselines, отрасль OpenAI database.
Большое спасибо OpenAI и DeepMind за открытое программное обеспечение, предоставленное исследователям глубокого обучения за последние несколько лет. Если вы еще не видели их удивительные достижения с такими технологиями, как AlphaGo, OpenAI Five и AlphaStar, вы, возможно, жили в течение последнего года вне изоляции, но вы должны их увидеть.
Обучение AlphaStarhttps://deepmind.com/blog/alphastar-mastering-real-time-strategy-game-starcraft-ii/
Несмотря на то, что мы не создадим ничего впечатляющего, торговля биткоин-роботами в повседневной торговле все еще нелегкая задача.
Поэтому мы должны не только научиться торговать сами... но и позволить роботам торговать за нас.
1.为我们的机器人创建gym环境以供其进行机器学习
2.渲染一个简单而优雅的可视化环境
3.训练我们的机器人,使其学习一个可获利的交易策略
Если вы еще не знакомы с тем, как создавать окружающие среды для занятий спортом с нуля, или с простыми визуализациями этих условий. Перед тем как продолжать, пожалуйста, поищите в Google статью, подобную этой.
在本教程中,我们将使用Zielak生成的Kaggle数据集。如果您想下载源代码,我的Github仓库中会提供,同时也有.csv数据文件。好的,让我们开始吧。
Сначала давайте введем все необходимые библиотеки.
import gym
import pandas as pd
import numpy as np
from gym import spaces
from sklearn import preprocessing
Далее, давайте создадим наши классы для среды. Нам нужно будет ввести параметры данных панды, а также опциональный initial_balance и lookback_window_size, которые будут указывать количество прошлых временных шагов, которые робот наблюдал на каждом шаге. Мы будем принимать комиссию за каждую сделку как 0.075%, то есть текущий курс Bitmex, и будем принимать параметры строки как false, что означает, что в случае по умолчанию наш параметр данных будет проходить через случайные фрагменты.
Мы также вызываем данные dropna (()) и reset_index (()) и сначала удаляем строки с значениями NaN, а затем переставляем индексы с количеством очками, поскольку мы удалили данные.
class BitcoinTradingEnv(gym.Env):
"""A Bitcoin trading environment for OpenAI gym"""
metadata = {'render.modes': ['live', 'file', 'none']}
scaler = preprocessing.MinMaxScaler()
viewer = None
def __init__(self, df, lookback_window_size=50,
commission=0.00075,
initial_balance=10000
serial=False):
super(BitcoinTradingEnv, self).__init__()
self.df = df.dropna().reset_index()
self.lookback_window_size = lookback_window_size
self.initial_balance = initial_balance
self.commission = commission
self.serial = serial
# Actions of the format Buy 1/10, Sell 3/10, Hold, etc.
self.action_space = spaces.MultiDiscrete([3, 10])
# Observes the OHCLV values, net worth, and trade history
self.observation_space = spaces.Box(low=0, high=1, shape=(10, lookback_window_size + 1), dtype=np.float16)
Наш action_space здесь представлен как группа из 3 вариантов (купить, продать или удержать) и другая группа из 10 сумм (1/10, 2/10, 3/10 и т. д.). Когда мы выбираем покупку, мы указываем buy amount * self.balance worth of BTC.
Наше observation_space определено как последовательный набор плавучих точек от 0 до 1, который имеет форму ((10, lookback_window_size + 1)); + 1 используется для расчета текущей продолжительности времени. Для каждой продолжительности времени в окне мы будем наблюдать значение OHCLV. Наша чистая стоимость равна количеству BTC, купленных или проданных, и сумме долларов, которые мы потратили или получили на эти BTC.
Далее нам нужно написать метод "резета" для инициализации среды.
def reset(self):
self.balance = self.initial_balance
self.net_worth = self.initial_balance
self.btc_held = 0
self._reset_session()
self.account_history = np.repeat([
[self.net_worth],
[0],
[0],
[0],
[0]
], self.lookback_window_size + 1, axis=1)
self.trades = []
return self._next_observation()
Здесь мы используем self._reset_session и self._next_observation, которые мы еще не определили.
我们环境的一个重要部分是交易会话的概念。如果我们将这个机器人部署到市场外,我们可能永远不会一次运行它超过几个月。出于这个原因,我们将限制self.df中连续帧数的数量,也就是我们的机器人连续一次能看到的帧数。
В нашем методе _reset_session мы сначала переставляем current_step на 0; затем мы назначаем steps_left на случайный номер от 1 до MAX_TRADING_SESSION, который мы будем определять в верхней части программы.
MAX_TRADING_SESSION = 100000 # ~2个月
Далее, если мы хотим непрерывно проходить х, мы должны настроить это на прохождение всего х, иначе мы настроим frame_start на случайную точку в self.df и создадим новый х данных, названный active_df, который является просто кусочком self.df и получается от frame_start до frame_start + steps_left.
def _reset_session(self):
self.current_step = 0
if self.serial:
self.steps_left = len(self.df) - self.lookback_window_size - 1
self.frame_start = self.lookback_window_size
else:
self.steps_left = np.random.randint(1, MAX_TRADING_SESSION)
self.frame_start = np.random.randint(self.lookback_window_size, len(self.df) - self.steps_left)
self.active_df = self.df[self.frame_start - self.lookback_window_size:self.frame_start + self.steps_left]
Одним из важных побочных эффектов прохождения цифр в случайных вырезках данных будет то, что наши роботы будут иметь больше уникальных данных для использования при длительной тренировке. Например, если мы просто будем проходить циферблат данных в последовательном порядке (т.е. в порядке от 0 до len (df)), то у нас будет только столько же единственных точек данных, сколько и в циферблатах данных.
Однако, произвольно просматривая фрагменты данного набора, мы можем создать более значимые наборы результатов сделок для каждой временной ступени первоначального набора данных, то есть комбинации поведения сделок и поведения цен, которые мы видели ранее, чтобы создать более уникальные наборы данных.
При 10-ти шагах времени после перезагрузки последовательной среды, наш робот будет всегда работать одновременно в наборе данных, и после каждого шага времени у него будет три варианта: купить, продать или удержать. Для каждого из этих трех вариантов требуется другой выбор: 10%, 20%... или 100% конкретной величины. Это означает, что наш робот может столкнуться с любым из 10 случаев из 103, в общей сложности 1030 случаев.
Теперь вернемся к нашей среде случайного выреза; при 10-часовом шаге наш робот может находиться в любом из 30-секундных состояний любого из len (df) временных шагов в диапазоне данных. Предположим, что после каждого временного шага будет сделан тот же выбор, что означает, что робот может пережить одно из 30-секундных состояний любого len (df) временных шагов в те же 10 временных шагов.
Несмотря на то, что это может быть довольно шумно для больших наборов данных, я считаю, что роботам следует позволить учиться большему количеству наших ограниченных данных. Мы по-прежнему просматриваем наши тестовые данные в последовательном порядке, чтобы получить свежие, кажущиеся в режиме реального времени, в надежде получить более точное понимание эффективности алгоритмов.
Обычно полезно наблюдать за эффективной визуальной средой, чтобы понять тип функций, которые будет использовать наш робот. Например, это визуализация наблюдаемого пространства с использованием рендеров OpenCV.
Наблюдения за средой визуализации OpenCV
Каждая строка в изображении представляет собой строку в нашем observation_space; первые 4 строки с аналогичной частотой, красные, представляют данные OHCL, а оранжевые и желтые точки ниже представляют собой транзакции; колеблющаяся синяя строка ниже представляет собой чистую стоимость робота, а более легкая строка ниже представляет собой транзакцию робота.
Если вы внимательно посмотрите, вы даже сможете создать свою собственную карту. Под панелью объема транзакций находится интерфейс, похожий на Морзе, который показывает историю транзакций. Похоже, что наш робот должен быть в состоянии полностью изучить данные из нашего observation_space, так что давайте продолжим. Здесь мы будем определять метод_next_observation, и мы будем масштабировать наблюдаемые данные с 0 до 1.
def _next_observation(self):
end = self.current_step + self.lookback_window_size + 1
obs = np.array([
self.active_df['Open'].values[self.current_step:end],
self.active_df['High'].values[self.current_step:end],
self.active_df['Low'].values[self.current_step:end],
self.active_df['Close'].values[self.current_step:end],
self.active_df['Volume_(BTC)'].values[self.current_step:end],])
scaled_history = self.scaler.fit_transform(self.account_history)
obs = np.append(obs, scaled_history[:, -(self.lookback_window_size + 1):], axis=0)
return obs
Мы создали свое пространство наблюдения, и теперь пришло время написать нашу функцию лестницы, а затем выполнить действия, назначенные роботом. Каждый раз, когда мы продаем свои BTC в текущем торговом периоде self.steps_left == 0, мы будем звонить_reset_session ((((((((((((((((((((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
def step(self, action):
current_price = self._get_current_price() + 0.01
self._take_action(action, current_price)
self.steps_left -= 1
self.current_step += 1
if self.steps_left == 0:
self.balance += self.btc_held * current_price
self.btc_held = 0
self._reset_session()
obs = self._next_observation()
reward = self.net_worth
done = self.net_worth <= 0
return obs, reward, done, {}
Совершение сделки так же просто, как получить текущую цену, определить действия, которые нужно выполнить, а также количество покупок или продаж. Давайте быстро напишем_take_action, чтобы мы могли проверить нашу среду.
def _take_action(self, action, current_price):
action_type = action[0]
amount = action[1] / 10
btc_bought = 0
btc_sold = 0
cost = 0
sales = 0
if action_type < 1:
btc_bought = self.balance / current_price * amount
cost = btc_bought * current_price * (1 + self.commission)
self.btc_held += btc_bought
self.balance -= cost
elif action_type < 2:
btc_sold = self.btc_held * amount
sales = btc_sold * current_price * (1 - self.commission)
self.btc_held -= btc_sold
self.balance += sales
最后,在同一方法中,我们会将交易附加到self.trades并更新我们的净值和账户历史。
if btc_sold > 0 or btc_bought > 0:
self.trades.append({
'step': self.frame_start+self.current_step,
'amount': btc_sold if btc_sold > 0 else btc_bought,
'total': sales if btc_sold > 0 else cost,
'type': "sell" if btc_sold > 0 else "buy"
})
self.net_worth = self.balance + self.btc_held * current_price
self.account_history = np.append(self.account_history, [
[self.net_worth],
[btc_bought],
[cost],
[btc_sold],
[sales]
], axis=1)
Наши роботы теперь могут запускать новую среду, постепенно завершать ее и принимать действия, влияющие на окружающую среду.
Наш метод рендеринга может быть таким же простым, как вызов print ((self.net_worth), но это не достаточно интересно. Вместо этого мы будем рисовать простой график, который содержит отдельный график, который содержит объем сделок и наши чистые деньги.
我们将从我上一篇文章中获取StockTradingGraph.py中的代码,并重新设计它以适应比特币环境。你可以从我的Github中获取代码。
Первое изменение, которое мы сделаем, это обновить self.df [
from datetime import datetime
Во-первых, мы импортируем библиотеку datetime, а затем мы используем метод utcfromtimestamp для получения строк UTC из каждого временного пакета и strftime в формате: Y-m-d.
date_labels = np.array([datetime.utcfromtimestamp(x).strftime('%Y-%m-%d %H:%M') for x in self.df['Timestamp'].values[step_range]])
Наконец, мы изменили self.df[
def render(self, mode='human', **kwargs):
if mode == 'human':
if self.viewer == None:
self.viewer = BitcoinTradingGraph(self.df,
kwargs.get('title', None))
self.viewer.render(self.frame_start + self.current_step,
self.net_worth,
self.trades,
window_size=self.lookback_window_size)
Ого! Теперь мы можем смотреть, как наши роботы торгуют биткойнами.
Визуализация наших роботов с помощью Matplotlib
Зеленый фаллоимитатор - это покупка BTC, а красный фаллоимитатор - это продажа. Белый фаллоимитатор в правом верхнем углу - это текущая стоимость бота, а нижний фаллоимитатор - это текущая цена биткойна. Просто и элегантно.
Одна из критик, которую я получил в предыдущей статье, была отсутствие перекрестной проверки, не разделяя данные на тренировочные и тестовые наборы. Целью этого было проверка точности конечной модели на новых данных, которые ранее никогда не были замечены. Хотя это не было предметом внимания в этой статье, это действительно было очень важно.
К примеру, распространенная форма перекрестной проверки называется k-fold проверка, в которой вы делите данные на k равных групп, каждая группа отдельно используется в качестве тестовой группы, а остальные данные используются в качестве тренировочной группы. Однако, данные временных последовательностей очень сильно зависят от времени, что означает, что более поздние данные очень сильно зависят от более ранних данных.
При применении данных временных последовательностей этот же недостаток применяется к большинству других стратегий перекрестной проверки. Таким образом, нам нужно использовать только часть целого числа кубиков данных в качестве тренировочного набора, начиная с кубиков и заканчивая какими-либо произвольными индексами, и использовать остальные данные в качестве тестовых наборов.
slice_point = int(len(df) - 100000)
train_df = df[:slice_point]
test_df = df[slice_point:]
Далее, поскольку наша среда настроена только на обработку отдельных томов данных, мы создадим две среды, одну для обучения данных, другую для тестирования данных.
train_env = DummyVecEnv([lambda: BitcoinTradingEnv(train_df, commission=0, serial=False)])
test_env = DummyVecEnv([lambda: BitcoinTradingEnv(test_df, commission=0, serial=True)])
现在,训练我们的模型就像使用我们的环境创建机器人并调用model.learn一样简单。
model = PPO2(MlpPolicy,
train_env,
verbose=1,
tensorboard_log="./tensorboard/")
model.learn(total_timesteps=50000)
Здесь мы используем распределительную таблицу, поэтому мы можем легко визуализировать нашу распределительную плату и просмотреть некоторые количественные показатели о наших роботах. Например, ниже приведены таблицы с дисконтированными вознаграждениями, на которые многие роботы проходят более 200 000 шагов:
Ого, похоже, что наши роботы очень выгодны! Наши лучшие роботы даже могут достичь баланса в 1000 раз больше 200 000 шагов, а остальные увеличиваются в среднем как минимум в 30 раз!
Именно тогда я осознал, что в окружающей среде есть ошибка... После исправления этой ошибки появилась новая награда:
Как вы видите, некоторые из наших роботов хорошо работают, а другие сами себя разрушают. Тем не менее, хорошие роботы могут достичь максимум 10 или даже 60 раз стартового баланса. Я должен признать, что все прибыльные роботы проходят обучение и тестирование без комиссий, так что наши роботы не могут заработать реальные деньги.
Давайте проверим наших роботов в тестовой среде (используя новые данные, которые они никогда раньше не видели), и посмотрим, как они будут себя вести.
Наши хорошо обученные роботы могут обанкротиться при обмене новыми данными.
Очевидно, что нам предстоит еще много работы. Мы можем значительно улучшить нашу производительность на этом наборе данных, просто переключив модель на использование стабильного базового A2C, а не нынешний робот PPO2. И, наконец, мы можем немного обновить нашу функцию вознаграждения, чтобы мы могли увеличить вознаграждение в чистой стоимости, а не просто достичь высокой чистой стоимости и остаться там, как это предложил Шон О'Шин Горман.
reward = self.net_worth - prev_net_worth
Только эти два изменения могут значительно улучшить производительность тестовых наборов данных, и, как вы увидите ниже, мы наконец-то можем получить выгоду от новых данных, которых не было в тренировочных наборах.
Но мы можем сделать лучше. Чтобы улучшить эти результаты, нам нужно оптимизировать наши суперпараметры и тренировать наших роботов дольше.
На данный момент эта статья довольно длинная, и у нас есть много деталей, которые нужно рассмотреть, поэтому мы собираемся здесь остановиться. В следующей статье мы будем использовать Bayes Optimization, чтобы выделить оптимальные суперпараметры для нашего проблемного пространства и подготовиться к обучению / тестированию на GPU с помощью CUDA.
В этой статье мы начали создавать прибыльный робот для торговли биткойнами с нуля, используя усиленное обучение.
1.使用OpenAI的gym从零开始创建比特币交易环境。
2.使用Matplotlib构建该环境的可视化。
3.使用简单的交叉验证对我们的机器人进行训练和测试。
4.略微调整我们的机器人以实现盈利
Хотя наши торговые роботы не были настолько прибыльными, насколько мы надеялись, мы движемся в правильном направлении. В следующий раз мы будем следить за тем, чтобы наши роботы всегда обыгрывали рынки, и мы увидим, как наши торговые роботы обрабатывают данные в режиме реального времени.