Создание роботов, которые будут торговать биткойнами без потерь

Автор:Доброта, Создано: 2019-06-27 10:58:40, Обновлено: 2023-10-30 20:30:00

img

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

В этом уроке мы будем создавать и применять усиливающийся учебный прибор, чтобы научиться создавать биткойн-торговый бот. В этом уроке мы будем использовать OpenAI gym и PPO бот из stable-baselines, отрасль OpenAI database.

Большое спасибо OpenAI и DeepMind за открытое программное обеспечение, предоставленное исследователям глубокого обучения за последние несколько лет. Если вы еще не видели их удивительные достижения с такими технологиями, как AlphaGo, OpenAI Five и AlphaStar, вы, возможно, жили в течение последнего года вне изоляции, но вы должны их увидеть.

img

Обучение AlphaStarhttps://deepmind.com/blog/alphastar-mastering-real-time-strategy-game-starcraft-ii/

Несмотря на то, что мы не создадим ничего впечатляющего, торговля биткоин-роботами в повседневной торговле все еще нелегкая задача.

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

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

Планы

img

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, которые мы еще не определили.

Встречи по торговле

img

我们环境的一个重要部分是交易会话的概念。如果我们将这个机器人部署到市场外,我们可能永远不会一次运行它超过几个月。出于这个原因,我们将限制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.

img

Наблюдения за средой визуализации 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 [Date tag] на self.df [Timestamp tag] и удалить все вызовы к date2num, поскольку наша дата уже в формате Unix time tag.

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[Volume] на self.df[Volume_(BTC) ] для соответствия нашему набору данных, и мы готовы.

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)

Ого! Теперь мы можем смотреть, как наши роботы торгуют биткойнами.

img

Визуализация наших роботов с помощью 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 шагов:

img

Ого, похоже, что наши роботы очень выгодны! Наши лучшие роботы даже могут достичь баланса в 1000 раз больше 200 000 шагов, а остальные увеличиваются в среднем как минимум в 30 раз!

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

img

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

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

img

Наши хорошо обученные роботы могут обанкротиться при обмене новыми данными.

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

reward = self.net_worth - prev_net_worth

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

img

Но мы можем сделать лучше. Чтобы улучшить эти результаты, нам нужно оптимизировать наши суперпараметры и тренировать наших роботов дольше.

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

Выводы

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

1.使用OpenAI的gym从零开始创建比特币交易环境。

2.使用Matplotlib构建该环境的可视化。

3.使用简单的交叉验证对我们的机器人进行训练和测试。

4.略微调整我们的机器人以实现盈利

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


Связанные

Больше