Xây dựng một robot giao dịch Bitcoin không mất tiền

Tác giả:Tốt, Tạo: 2019-06-27 10:58:40, Cập nhật: 2023-10-30 20:30:00

img

Chúng ta hãy sử dụng học tập tăng cường trong AI để tạo ra một robot giao dịch tiền kỹ thuật số.

Trong bài viết này, chúng ta sẽ tạo và áp dụng một con số tăng cường để học cách tạo một robot giao dịch Bitcoin. Trong bài hướng dẫn này, chúng ta sẽ sử dụng OpenAI gym và PPO robot từ stable-baselines, một nhánh của OpenAI database.

Cảm ơn OpenAI và DeepMind đã cung cấp phần mềm nguồn mở cho các nhà nghiên cứu học sâu trong vài năm qua. Nếu bạn chưa thấy những thành tựu đáng kinh ngạc mà họ đã đạt được với các công nghệ như AlphaGo, OpenAI Five và AlphaStar, bạn có thể đã sống ngoài sự cô lập trong năm qua, nhưng bạn nên xem chúng.

img

AlphaStar đào tạohttps://deepmind.com/blog/alphastar-mastering-real-time-strategy-game-starcraft-ii/

Mặc dù chúng ta sẽ không tạo ra bất cứ thứ gì ấn tượng, nhưng giao dịch với robot Bitcoin trong giao dịch hàng ngày vẫn là một điều không dễ dàng.

Những thứ có được quá đơn giản không có giá trị gì cả.

Vì vậy, chúng ta không chỉ cần học cách tự giao dịch... mà còn phải để robot giao dịch cho chúng ta.

Kế hoạch

img

1.为我们的机器人创建gym环境以供其进行机器学习

2.渲染一个简单而优雅的可视化环境

3.训练我们的机器人,使其学习一个可获利的交易策略

Nếu bạn chưa quen với cách tạo môi trường phòng tập thể dục từ đầu, hoặc cách đơn giản để hiển thị các môi trường này. Trước khi tiếp tục, vui lòng google một bài viết như thế này. Cả hai hành động này sẽ không khó khăn ngay cả khi là một lập trình viên mới.

Nhập vào

在本教程中,我们将使用Zielak生成的Kaggle数据集。如果您想下载源代码,我的Github仓库中会提供,同时也有.csv数据文件。好的,让我们开始吧。

Trước tiên, hãy nhập tất cả các thư viện cần thiết. Hãy chắc chắn cài đặt bất kỳ thư viện nào bạn thiếu bằng pip.

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

Tiếp theo, hãy tạo lớp của chúng tôi cho môi trường. Chúng tôi cần truyền một số phế độ dữ liệu của pandas, cùng với một initial_balance tùy chọn và một lookback_window_size, nó sẽ chỉ ra số bước thời gian quá khứ mà robot quan sát trong mỗi bước. Chúng tôi sẽ mặc định mức phí cho mỗi giao dịch là 0.075%, tức là tỷ giá hối đoái hiện tại của Bitmex, và sẽ mặc định các tham số chuỗi là false, có nghĩa là theo mặc định số phế độ dữ liệu của chúng tôi sẽ đi qua một đoạn ngẫu nhiên.

Chúng ta cũng gọi cho dữ liệu dropna (()) và reset_index (()) và trước tiên xóa hàng có giá trị NaN, sau đó đặt lại chỉ mục với số lần, vì chúng ta đã xóa dữ liệu.

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 của chúng ta ở đây được thể hiện như là một tập hợp 3 lựa chọn (mua, bán hoặc giữ) và một tập hợp 10 số tiền khác (1/10, 2/10, 3/10, v.v.). Khi chọn hành động mua, chúng ta sẽ buy amount * self.balance worth of BTC. Đối với hành động bán, chúng ta sẽ sell amount * self.btc_held worth of BTC.

Observation_space của chúng tôi được định nghĩa là một tập hợp các điểm nổi liên tục từ 0 đến 1, hình dạng là ((10, lookback_window_size + 1)); + 1 để tính bước thời gian hiện tại. Đối với mỗi bước thời gian trong cửa sổ, chúng tôi sẽ quan sát giá trị OHCLV.

Tiếp theo, chúng ta cần viết một phương pháp reset để khởi tạo môi trường.

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()

Ở đây chúng ta sử dụng self._reset_session và self._next_observation, chúng ta chưa định nghĩa chúng.

Các cuộc đàm phán

img

我们环境的一个重要部分是交易会话的概念。如果我们将这个机器人部署到市场外,我们可能永远不会一次运行它超过几个月。出于这个原因,我们将限制self.df中连续帧数的数量,也就是我们的机器人连续一次能看到的帧数。

Trong phương pháp _reset_session của chúng tôi, chúng tôi sẽ đặt lại current_step là 0; tiếp theo, chúng tôi sẽ đặt steps_left là một số lượng ngẫu nhiên từ 1 đến MAX_TRADING_SESSION, phần này chúng tôi sẽ xác định ở đầu chương trình.

MAX_TRADING_SESSION = 100000 # ~2个月

Tiếp theo, nếu chúng ta muốn liên tục đi qua các tần số, chúng ta phải thiết lập để đi qua toàn bộ tần số, nếu không chúng ta sẽ thiết lập frame_start như là một điểm ngẫu nhiên trong self.df và tạo ra một tần số dữ liệu mới có tên là active_df, nó chỉ là một mảnh của self.df và được lấy từ frame_start đến 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]

Một tác dụng phụ quan trọng của việc đi qua số lượng dữ liệu trong các đoạn ngẫu nhiên là robot của chúng ta sẽ có nhiều dữ liệu độc đáo hơn để sử dụng trong quá trình đào tạo lâu dài. Ví dụ, nếu chúng ta chỉ đi qua số lượng dữ liệu (tức là theo thứ tự từ 0 đến len (df)) theo chuỗi, thì chúng ta sẽ chỉ có nhiều điểm dữ liệu duy nhất trong số lượng dữ liệu. Không gian quan sát của chúng ta thậm chí chỉ có thể sử dụng số trạng thái khác nhau trong mỗi bước thời gian.

Tuy nhiên, bằng cách ngẫu nhiên đi qua các đoạn dữ liệu, chúng ta có thể tạo ra một tập hợp kết quả giao dịch có ý nghĩa hơn cho mỗi bước thời gian trong tập dữ liệu ban đầu, đó là sự kết hợp của hành vi giao dịch và hành vi giá mà chúng ta đã thấy trước đây để tạo ra các tập dữ liệu độc đáo hơn.

Khi bước thời gian sau khi thiết lập lại môi trường chuỗi là 10, robot của chúng tôi sẽ luôn chạy đồng thời trong bộ dữ liệu, và sau mỗi bước thời gian có ba lựa chọn: mua, bán hoặc giữ. Đối với mỗi lựa chọn ba, cần có một lựa chọn khác: 10%, 20%,... hoặc 100% thực thi cụ thể. Điều này có nghĩa là robot của chúng tôi có thể gặp phải bất kỳ trạng thái nào trong số 10 lần trong số 103, tổng cộng 1030 trường hợp.

Bây giờ trở lại môi trường cắt tỉa ngẫu nhiên của chúng tôi. Khi bước thời gian là 10, robot của chúng tôi có thể nằm trong bước thời gian len (df) trong số lượng dữ liệu. Giả sử thực hiện cùng một lựa chọn sau mỗi bước thời gian, điều đó có nghĩa là robot có thể trải qua trạng thái duy nhất trong 30 giây của bất kỳ bước thời gian len (df) trong cùng 10 bước thời gian.

Mặc dù điều này có thể gây ra khá nhiều tiếng ồn cho các bộ dữ liệu lớn, nhưng tôi tin rằng chúng ta nên cho phép robot học hỏi nhiều hơn từ số lượng dữ liệu hạn chế của chúng ta. Chúng ta vẫn sẽ đi qua dữ liệu thử nghiệm của chúng ta theo cách chuỗi để có được dữ liệu mới nhất, có vẻ như là thời gian thực, với hy vọng có được một sự hiểu biết chính xác hơn về hiệu quả của thuật toán.

Những gì chúng ta nhìn thấy qua mắt robot

Việc quan sát môi trường thị giác hiệu quả thường rất hữu ích để hiểu loại chức năng mà robot của chúng ta sẽ sử dụng. Ví dụ, đây là hình ảnh của không gian quan sát được sử dụng trong công nghệ công nghệ OpenCV.

img

Nhìn vào môi trường trực quan OpenCV

Mỗi dòng trong hình tượng đại diện cho một dòng trong observation_space của chúng tôi. Bốn dòng màu đỏ có tần số tương tự trước đại diện cho dữ liệu OHCL, và các chấm màu cam và màu vàng bên dưới đại diện cho giao dịch. Các thanh màu xanh lơ lửng bên dưới là giá trị ròng của robot, và các thanh nhẹ hơn bên dưới đại diện cho giao dịch của robot.

Nếu bạn quan sát kỹ hơn, bạn thậm chí có thể tự tạo ra một biểu đồ. Dưới thanh khối lượng giao dịch là một giao diện tương tự mã Morse, hiển thị lịch sử giao dịch. Dường như robot của chúng tôi nên có thể học đầy đủ từ dữ liệu trong observation_space của chúng tôi, vì vậy hãy tiếp tục.

  • Điều quan trọng là chỉ mở rộng dữ liệu mà robot đã quan sát được cho đến nay để ngăn chặn sự sai lệch quá khứ.
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

Hành động

Chúng tôi đã xây dựng không gian quan sát của mình, bây giờ là lúc viết hàm thang của chúng tôi và thực hiện các hành động được đặt ra cho robot. Mỗi khi chúng tôi tự.steps_left == 0 trong giờ giao dịch hiện tại, chúng tôi sẽ bán BTC mà chúng tôi đang nắm giữ và gọi_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, {}

Thực hiện hành động giao dịch là như đơn giản như lấy current_price, xác định hành động cần thực hiện, và số lượng mua hoặc bán. Hãy nhanh chóng viết_take_action để chúng ta có thể kiểm tra môi trường của mình.

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)

Robot của chúng ta bây giờ có thể khởi động một môi trường mới, hoàn thành môi trường đó từng bước và thực hiện các hành động ảnh hưởng đến môi trường.

Xem các giao dịch robot của chúng tôi

Phương pháp hiển thị của chúng ta có thể đơn giản như gọi print ((self.net_worth), nhưng điều đó không đủ thú vị. Thay vào đó, chúng ta sẽ vẽ một biểu đồ đơn giản, bao gồm các biểu đồ riêng biệt về khối lượng giao dịch và giá trị ròng của chúng ta.

我们将从我上一篇文章中获取StockTradingGraph.py中的代码,并重新设计它以适应比特币环境。你可以从我的Github中获取代码。

Thay đổi đầu tiên chúng tôi sẽ làm là cập nhật self.df [Date tag] thành self.df [Timestamp tag] và xóa tất cả các cuộc gọi đến date2num vì ngày của chúng tôi đã được định dạng thời gian Unix.

from datetime import datetime

Đầu tiên, nhập thư viện datetime, sau đó chúng ta sẽ sử dụng phương pháp utcfromtimestamp để lấy các chuỗi UTC từ mỗi chuông thời gian và strftime, định dạng chúng thành: Y-m-d H: M định dạng chuỗi.

date_labels = np.array([datetime.utcfromtimestamp(x).strftime('%Y-%m-%d %H:%M') for x in self.df['Timestamp'].values[step_range]])

Cuối cùng, chúng tôi đã thay đổi self.df[Volume] thành self.df[Volume_(BTC) ] để phù hợp với bộ dữ liệu của chúng tôi, chúng tôi đã hoàn thành và chúng tôi đã sẵn sàng.

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)

Wow! Bây giờ chúng ta có thể xem robot của chúng ta giao dịch Bitcoin.

img

Matplotlib để hình dung các giao dịch robot của chúng tôi

Biểu tượng màu xanh lá cây đại diện cho việc mua BTC, và biểu tượng màu đỏ đại diện cho việc bán. Biểu tượng màu trắng ở góc trên bên phải là giá trị ròng hiện tại của robot, và biểu tượng ở góc dưới bên phải là giá hiện tại của Bitcoin. Dễ dàng và thanh lịch. Bây giờ, đã đến lúc đào tạo robot của chúng tôi và xem chúng ta có thể kiếm được bao nhiêu tiền!

Thời gian huấn luyện

Một lời chỉ trích mà tôi nhận được trong bài viết trước đó là thiếu xác minh chéo, không phân chia dữ liệu thành tập huấn và tập thử nghiệm. Mục đích của việc này là để kiểm tra tính chính xác của mô hình cuối cùng trên dữ liệu mới chưa từng thấy trước đây. Mặc dù đó không phải là mối quan tâm của bài viết, nhưng nó thực sự rất quan trọng.

Ví dụ, một hình thức xác minh chéo phổ biến được gọi là xác minh k-fold, trong đó bạn chia dữ liệu thành k nhóm tương đương, mỗi nhóm riêng biệt làm nhóm thử nghiệm và sử dụng dữ liệu còn lại làm nhóm huấn luyện. Tuy nhiên, dữ liệu chuỗi thời gian phụ thuộc rất nhiều vào thời gian, điều này có nghĩa là dữ liệu sau này phụ thuộc rất nhiều vào dữ liệu trước đó. Vì vậy, k-fold sẽ không hoạt động vì robot của chúng tôi sẽ học từ dữ liệu tương lai trước khi giao dịch, đây là một lợi thế không công bằng.

Khi áp dụng cho dữ liệu chuỗi thời gian, các lỗi tương tự cũng áp dụng cho hầu hết các chiến lược xác minh chéo khác. Do đó, chúng ta chỉ cần sử dụng một phần số khoan dữ liệu hoàn chỉnh làm tập huấn bắt đầu từ số khoan đến một số chỉ mục tùy chọn, và sử dụng số liệu còn lại làm tập thử nghiệm.

slice_point = int(len(df) - 100000)
train_df = df[:slice_point]
test_df = df[slice_point:]

Tiếp theo, vì môi trường của chúng tôi chỉ được thiết lập để xử lý số lượng dữ liệu đơn lẻ, chúng tôi sẽ tạo ra hai môi trường, một cho dữ liệu đào tạo và một cho dữ liệu thử nghiệm.

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)

Ở đây, chúng tôi sử dụng bảng bước, vì vậy chúng tôi có thể dễ dàng trực quan hóa đồ lưu lượng của chúng tôi và xem một số chỉ số định lượng về robot của chúng tôi. Ví dụ, dưới đây là biểu đồ phần thưởng giảm giá cho nhiều robot vượt quá 200.000 bước thời gian:

img

Vâng, có vẻ như robot của chúng tôi rất có lợi! Robot tốt nhất của chúng tôi thậm chí có thể đạt được sự cân bằng 1000x trong quá trình 200.000 bước, và trung bình còn lại tăng ít nhất 30 lần!

Khi đó, tôi nhận ra rằng có một lỗi trong môi trường... sau khi sửa lỗi đó, đây là biểu đồ thưởng mới:

img

Như bạn có thể thấy, một số robot của chúng tôi làm tốt, một số khác tự phá sản. Tuy nhiên, một robot hoạt động tốt có thể đạt 10 lần hoặc thậm chí 60 lần số dư ban đầu. Tôi phải thừa nhận rằng tất cả các robot có lợi nhuận đều được đào tạo và thử nghiệm mà không cần hoa hồng, vì vậy nó không thực tế để robot của chúng tôi kiếm được bất kỳ tiền thật sự nào.

Hãy thử nghiệm robot của chúng tôi trong môi trường thử nghiệm (sử dụng dữ liệu mới mà chúng chưa từng thấy trước đây) và xem chúng sẽ hoạt động như thế nào.

img

Những con robot được đào tạo của chúng tôi sẽ bị phá sản khi giao dịch dữ liệu thử nghiệm mới.

Rõ ràng, chúng ta vẫn còn nhiều việc phải làm. Bằng cách đơn giản chuyển đổi mô hình để sử dụng A2C trên đường cơ sở ổn định, thay vì robot PPO2 hiện tại, chúng ta có thể cải thiện đáng kể hiệu suất của mình trên bộ dữ liệu này. Cuối cùng, theo lời khuyên của Sean O'Gorman, chúng ta có thể cập nhật chức năng phần thưởng của mình một chút để chúng ta có thể tăng phần thưởng trong giá trị ròng chứ không chỉ đạt được giá trị ròng cao và ở đó.

reward = self.net_worth - prev_net_worth

Chỉ với hai thay đổi này, chúng ta có thể cải thiện đáng kể hiệu suất của bộ dữ liệu thử nghiệm, và như bạn sẽ thấy dưới đây, chúng ta cuối cùng đã có thể tận dụng được dữ liệu mới mà tập trung đào tạo không có.

img

Tuy nhiên, chúng ta có thể làm tốt hơn. Để cải thiện kết quả này, chúng ta cần tối ưu hóa các siêu tham số và đào tạo robot của chúng ta lâu hơn.

Đến lúc này, bài viết này đã hơi dài, và chúng ta vẫn còn nhiều chi tiết cần xem xét, vì vậy chúng ta sẽ nghỉ ngơi ở đây. Trong bài viết tiếp theo, chúng ta sẽ sử dụng tối ưu hóa Bayes để phân loại các siêu tham số tốt nhất cho không gian vấn đề của chúng ta và chuẩn bị cho việc đào tạo / kiểm tra trên GPU bằng CUDA.

Kết luận

Trong bài viết này, chúng tôi bắt đầu sử dụng học tập tăng cường để tạo ra một robot giao dịch Bitcoin có lợi nhuận từ đầu.

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

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

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

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

Mặc dù robot giao dịch của chúng tôi không có lợi nhuận như chúng tôi mong muốn, nhưng chúng tôi đã đi đúng hướng. Lần tới, chúng tôi sẽ đảm bảo rằng robot của chúng tôi luôn đánh bại thị trường, và chúng tôi sẽ thấy cách robot giao dịch của chúng tôi xử lý dữ liệu thời gian thực.


Có liên quan

Thêm nữa