主页 > token.im官网 > 框架在另一个框架之上_创建一个不会赔钱的比特币交易机器人

框架在另一个框架之上_创建一个不会赔钱的比特币交易机器人

token.im官网 2023-01-17 11:09:07

c68c7120b7b3ab2f7b42549df16a648a.png

在本文中,我们将创建并应用强化学习帧速率来学习如何制作比特币交易机器人。在本教程中,我们将使用 OpenAI 的 gym 和 stable-baselines 库中的 PPO 机器人,它是 OpenAI 基线库的一个分支。

非常感谢 OpenAI 和 DeepMind 在过去几年中为深度学习研究人员提供的开源软件。如果您还没有看到他们在 AlphaGo、OpenAI Five 和 AlphaStar 等技术方面取得的成就,那么您去年可能一直处于孤立状态,但您也应该检查一下。

3504bc77e2cd7cd80ad476ca9cf99d55.png

AlphaStar 培训

虽然我们不会创造任何令人印象深刻的东西,但比特币机器人的交易仍然是一件容易的事。然而,正如泰迪罗斯福曾经说过的,

任何太简单的东西都没有价值。

所以不仅要学会为自己交易……还要让机器人为我们交易。

计划

1.为我们的机器学习机器人创建一个健身房环境

2.渲染简洁大方

3.训练我们的机器人学习盈利的交易策略

如果您还不熟悉如何从头开始创建健身房环境,或者如何简单地呈现这些环境的可视化。在继续之前,请随意谷歌其中一篇文章。即便是最初级的程序员,这两个动作也不难。

开始

在本教程中,我们将使用 Zielak 生成的 Kaggle 数据集。如果您想下载源代码,可以在我的 Github 存储库中找到它以及 .csv 数据文件。好的,让我们开始吧。

首先,让我们导入所有必要的库。确保使用 pip 安装您缺少的所有库。

现在拥有比特币怎么出售

import gym
import pandas as pd
import numpy as np
from gym import spaces
from sklearn import preprocessing

接下来,让我们为环境创建我们的类。我们需要传入一个 pandas 数量的数据帧,以及一个可选的 initial_balance 和一个lookback_window_size,这将指示机器人在每个步骤中观察到的过去时间步数。我们默认每笔交易的佣金为0.075%,即Bitmex当前的汇率,默认serial参数为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 等)。选择买入动作时,我们将买入金额 * self.balance 价值的 BTC。对于卖出行为,我们将卖出金额 * self.btc_held 价值的 BTC。当然,hold 动作会忽略数量,什么也不做。

我们的observation_space被定义为0到1之间的一组连续浮点数,形状为(10,lookback_window_size + 1). + 1用于计算当前时间步长。对于window中的每个时间步长,我们将观察 OHCLV 值。我们的权益等于购买或出售的 BTC 数量,以及我们在这些 BTC 上花费或收到的总美元金额。

接下来,我们需要编写reset方法来初始化环境。

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个月

MAX_TRADING_SESSION = 100000 # ~2个月

p>

接下来,如果我们想连续循环遍历帧,我们必须设置循环遍历整个帧,否则我们将 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 个时间步,我们的机器人将始终在数据集中并发运行现在拥有比特币怎么出售,并且每个时间步后有 3 个选项:买入、卖出或持有。对于这三个选项中的每一个,都需要另一个选项:具体执行量的 10%、20%、...或 100%。这意味着我们的机器人可能会遇到103到10种状态中的任何一种,总共1030种情况。

现在回到我们的随机切片环境。在时间步 10,我们的机器人可以在数据帧数量内的任何 len(df) 时间步。假设在每个时间步之后做出相同的选择现在拥有比特币怎么出售,这意味着机器人可以在相同的 10 个时间步中体验 len(df) 的 30 次方的任何独特状态。

虽然这可能会给大型数据集带来相当大的噪音,但我认为应该允许机器人从我们有限的数据中学习更多信息。我们仍然会以串行方式迭代我们的测试数据,以获得最新鲜、看似“实时”的数据,以期更准确地了解算法的有效性。

通过机器人的眼睛观察

通过有效的视觉环境进行观察通常有助于了解我们的机器人将使用的功能类型。例如,这里是用 OpenCV 渲染的可观察空间的可视化。

b514e70c7a06489f925944aade96ea5f.png

OpenCV 可视化环境观察

图像中的每一行代表我们观察空间中的一行。前 4 行中频率相似的红线代表 OHCL 数据,正下方的橙色和黄色圆点代表音量。下面波动的蓝色条是机器人的净值,而下面较浅的条代表机器人的交易。

如果您仔细观察,您甚至可以自己制作烛台图。音量条下方是一个类似摩尔斯电码的界面,显示交易历史。看起来我们的机器人应该能够从我们的观察空间中的数据中充分学习,所以让我们继续。在这里,我们将定义 _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

采取行动

我们已经建立了我们的观察空间,现在是时候编写我们的步骤函数并采取机器人预定的动作了。每当我们当前交易时段的 self.steps_left == 0 时,我们将出售我们持有的 BTC 并调用 _reset_session()。否则,我们将奖励设置为我们当前的权益,如果资金用完,我们将完成设置为 True。

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, {}

现在拥有比特币怎么出售

进行交易操作就像获取 current_price、确定需要执行的操作以及买入或卖出的金额一样简单。让我们快速编写 _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'] 更新为 self.df['Timestamp'] 并删除对 date2num 的所有调用,因为我们的日期已经是 unix 时间戳格式。接下来,在我们的渲染方法中,我们将更新日期标签以打印人类可读的日期而不是数字。

from datetime import datetime

首先,导入datetime库,然后我们将使用utcfromtimestamp方法从每个timestamp中获取UTC字符串,strftime格式化为:Y-m-d H:M格式的字符串。

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)'] 以匹配我们的数据集,完成后,我们就可以开始了。回到我们的 BitcoinTradingEnv,我们现在可以编写 render 方法来显示图表。

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)

看!我们现在可以观看我们的机器人交易比特币了。

14056330460f8ac79c50a63a8162e08a.png

现在拥有比特币怎么出售

使用 Matplotlib 可视化我们的机器人交易

绿色幻影标签代表 BTC 买入,红色幻影标签代表卖出。右上角的白色标签是机器人当前的权益,右下角的标签是比特币的当前价格。简单而优雅。现在,是时候训练我们的机器人了,看看我们能赚多少钱!

训练时间

我在上一篇文章中收到的一个批评是缺乏交叉验证,没有将数据分成训练集和测试集。这样做的目的是在它从未见过的新数据上测试最终模型的准确性。虽然这不是那篇文章的重点,但它确实很重要。由于我们使用的是时间序列数据,因此在交叉验证方面我们没有太多选择。

例如,交叉验证的一种常见形式称为 k 折验证,其中您将数据分成 k 个相等的组,将每个组分开作为测试组,并将其余数据用作训练集但是,时间序列数据高度依赖时间,这意味着后面的数据高度依赖前面的数据。所以 k-fold 不起作用,因为我们的机器人会在交易前从未来的数据中学习,这是一个不公平的优势。

当应用于时间序列数据时,同样的陷阱也适用于大多数其他交叉验证策略。所以我们只使用完整数据帧号的一部分作为训练集,从帧号开始到某个任意索引,其余的数据作为测试集。

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)

这里我们使用 tensorboard,因此我们可以轻松地可视化我们的 tensorflow 图并查看有关我们机器人的一些定量指标。例如,下面是许多机器人超过 200,000 个时间步的折扣奖励图表:

6c81453ba0a5af63de9080452c3065c0.png

哇,看起来我们的机器人很赚钱!我们最好的机器人甚至能够在 200,000 步的过程中实现 1000 倍的平衡,其余的平均至少 30 倍!

那时我才意识到环境中存在错误...修复该错误后,这是新的奖励图表:

2b4b2bc3c100a5293d7bbca0625207af.png@ >

现在拥有比特币怎么出售

如您所见,我们的一些机器人做得很好,其余的则自行破产。但是,表现良好的机器人可以达到其初始余额的 10 倍甚至 60 倍。我必须承认,所有盈利的机器人都是在没有佣金的情况下进行培训和测试的,因此我们的机器人要真正赚钱是不切实际的。但我们至少找到了自己的路!

让我们在测试环境中测试我们的机器人(使用它们以前从未见过的新数据),看看它们的表现如何。

b1d0f09c10e599ce9360712fc1949cdb.png

我们训练有素的机器人在交易新的测试数据时会崩溃

显然我们还有很多工作要做。通过简单地切换模型以使用稳定的基线 A2C,而不是当前的 PPO2 机器人,我们可以大大提高我们在这个数据集上的性能。最后,按照 Sean O'Gorman 的建议,我们可以稍微更新一下我们的奖励函数,以便我们增加权益奖励,而不是仅仅获得高权益并停留在那里。

reward = self.net_worth - prev_net_worth

仅这两个变化就可以极大地提高测试数据集上的性能,正如您在下面看到的,我们终于能够在没有新数据盈利的情况下获得训练集。

0f492e70f4fcd0f37f805bc8689034b4.png

但我们可以做得更好。为了改善这些结果,我们需要优化我们的超参数并训练我们的机器人更长时间。是时候让 GPU 工作并启动了!

至此,这篇文章有点长,我们还有很多细节需要考虑,所以我们要在这里休息一下。在下一篇文章中,我们将使用贝叶斯优化为我们的问题空间划分最佳超参数,并为使用 CUDA 在 GPU 上进行训练/测试做准备。

结论

在本文中,我们着手使用强化学习从头开始创建一个盈利的比特币交易机器人。我们能够:

1.使用 OpenAI 的健身房从头开始创建比特币交易环境。

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

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

4.稍微调整我们的机器人以盈利

虽然我们的交易机器人没有我们希望的那样有利可图,但我们已经朝着正确的方向前进。下次我们将确保我们的机器人能够始终如一地击败市场,我们将看看我们的交易机器人如何处理实时数据。请继续关注我的下一篇文章,比特币万岁!