Tạo một robot giao dịch Bitcoin sẽ không mất tiền

Tác giả:Lydia., Tạo: 2023-02-01 11:52:21, Cập nhật: 2023-09-18 19:40:25

img

Tạo một robot giao dịch Bitcoin sẽ không mất tiền

Hãy sử dụng học tập củng cố trong AI để xây dựng một robot giao dịch tiền kỹ thuật số.

Trong bài viết này, chúng tôi sẽ tạo và áp dụng một số khung học tập nâng cao để học cách tạo robot giao dịch Bitcoin. Trong hướng dẫn này, chúng tôi sẽ sử dụng phòng tập OpenAI và robot PPO từ thư viện cơ sở ổn định, là một nhánh của thư viện cơ sở OpenAI.

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

img

AlphaStar huấn luyện:https://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ứ điều gì ấn tượng, nhưng vẫn không dễ dàng để giao dịch robot Bitcoin trong các giao dịch hàng ngày.

Không có giá trị trong bất cứ điều gì quá đơn giản.

Do đó, chúng ta không chỉ nên học cách tự giao dịch, mà còn để robot giao dịch cho chúng ta.

Kế hoạch

img

  1. Tạo một môi trường tập thể dục cho robot của chúng tôi để thực hiện máy học

  2. Làm cho một môi trường trực quan đơn giản và thanh lịch

  3. Đào tạo robot của chúng tôi để học một chiến lược giao dịch có lợi nhuận

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

Bắt đầu

Trong hướng dẫn này, chúng ta sẽ sử dụng bộ dữ liệu Kaggle được tạo bởi Zielak. Nếu bạn muốn tải xuống mã nguồn, nó sẽ được cung cấp trong kho lưu trữ Github của tôi, cùng với tệp dữ liệu.csv. Được rồi, hãy bắt đầu.

Đầu tiên, hãy nhập tất cả các thư viện cần thiết. Hãy chắc chắn sử dụng pip để cài đặt bất kỳ thư viện nào bạn đang thiếu.

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 chuyển vào một số khung dữ liệu Pandas và một initial_balance tùy chọn và một lookback_window_size, sẽ chỉ ra số bước thời gian qua được robot quan sát trong mỗi bước. Chúng tôi mặc định ủy ban của 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à mặc định tham số hàng loạt là sai, có nghĩa là số khung dữ liệu của chúng tôi sẽ được đi qua bởi các mảnh ngẫu nhiên theo mặc định.

Chúng tôi cũng gọi dropna() và reset_index() trên dữ liệu, trước tiên xóa hàng với giá trị NaN, và sau đó đặt lại chỉ số số khung hình, bởi vì chúng tôi đã 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 tôi được thể hiện như một nhóm 3 tùy chọn (mua, bán hoặc giữ) ở đây và một nhóm khác gồm 10 số tiền (1/10, 2/10, 3/10, v.v.). Khi chúng tôi chọn mua, chúng tôi sẽ mua số tiền * self.balance word của BTC. Để bán, chúng tôi sẽ bán số tiền * self.btc_held giá trị của BTC. Tất nhiên, giữ sẽ bỏ qua số tiền và không làm gì.

Observation_space của chúng tôi được định nghĩa là một dấu chấm nổi liên tục được đặt giữa 0 và 1, và hình dạng của nó là (10, lookback_window_size+1). + 1 được sử dụng để 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. Tài sản ròng của chúng tôi bằng số lượng BTC chúng tôi mua hoặc bán và tổng số đô la chúng tôi chi tiêu hoặc nhận được trên các BTC này.

Tiếp theo, chúng ta cần viết phương thức 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, mà chúng ta chưa định nghĩa. Hãy định nghĩa chúng trước.

Buổi giao dịch

img

Một phần quan trọng của môi trường của chúng tôi là khái niệm các phiên giao dịch. Nếu chúng tôi triển khai robot này bên ngoài thị trường, chúng tôi có thể không bao giờ chạy nó trong hơn một vài tháng tại một thời điểm. Vì lý do này, chúng tôi sẽ giới hạn số lượng khung liên tiếp trong self.df, đó là số khung mà robot của chúng tôi có thể nhìn thấy tại một thời điểm.

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

MAX_TRADING_SESSION = 100000 # ~2 months

Tiếp theo, nếu chúng ta muốn đi qua số lượng khung hình liên tục, chúng ta phải đặt nó để đi qua toàn bộ số khung hình, nếu không chúng ta đặt frame_start vào một điểm ngẫu nhiên trong self.df và tạo một khung dữ liệu mới có tên là active_df, đó chỉ là một lát của self.df và nó sẽ đi 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 khung dữ liệu trong đ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 đào tạo dài hạn. Ví dụ, nếu chúng ta chỉ đi qua số lượng khung dữ liệu theo cách tuần tự (tức là từ 0 đến len ((df))), chúng ta sẽ chỉ có nhiều điểm dữ liệu độc đáo như số khung dữ liệu. Không gian quan sát của chúng ta chỉ có thể sử dụng một số trạng thái riêng biệt ở mỗi bước thời gian.

Tuy nhiên, bằng cách đi qua các lát của bộ dữ liệu ngẫu nhiên, chúng ta có thể tạo ra một bộ kết quả giao dịch có ý nghĩa hơn cho mỗi bước thời gian trong bộ dữ liệu ban đầu, đó là sự kết hợp của hành vi giao dịch và hành vi giá được nhìn thấy trước đây để tạo ra các bộ 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 hàng loạt là 10, robot của chúng tôi sẽ luôn chạy trong bộ dữ liệu cùng một lúc, và có ba tùy chọn sau mỗi bước thời gian: mua, bán hoặc giữ. Đối với mỗi ba tùy chọn, bạn cần một tùy chọn khác: 10%, 20%,... hoặc 100% số tiền thực hiện cụ thể. Điều này có nghĩa là robot của chúng tôi có thể gặp phải một trong 10 trạng thái của bất kỳ 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ể ở bất kỳ bước thời gian len ((df) nào trong số khung dữ liệu. Giả sử rằng cùng một lựa chọn được thực hiện sau mỗi bước thời gian, điều đó có nghĩa là robot có thể trải nghiệm trạng thái độc đáo của bất kỳ len ((df) đến cường độ 30 trong cùng 10 bước thời gian.

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

Nhìn qua con mắt của robot

Thông qua quan sát môi trường trực quan hiệu quả, nó thường hữu ích để hiểu loại chức năng mà robot của chúng tôi sẽ sử dụng.

Quan sát môi trường trực quan hóa OpenCV

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

Nếu bạn quan sát cẩn thận, bạn thậm chí có thể tự tạo bản đồ nến. Dưới thanh khối lượng giao dịch là giao diện mã Morse, hiển thị lịch sử giao dịch. Có vẻ như robot của chúng tôi nên có thể học đủ từ dữ liệu trong không gian quan sát của chúng tôi, vì vậy hãy tiếp tục. Ở đây chúng tôi sẽ xác định phương pháp quan sát tiếp theo, chúng tôi quy mô dữ liệu quan sát từ 0 đến 1.

  • Điều quan trọng là chỉ mở rộng dữ liệu được robot quan sát cho đến nay để ngăn ngừa sai lệch dẫn đầu.
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 đã thiết lập không gian quan sát của mình, và bây giờ là lúc viết hàm thang của chúng tôi, và sau đó thực hiện hành động theo lịch trình của robot. Bất cứ khi nào self.steps_left == 0 cho phiên giao dịch hiện tại của chúng tôi, chúng tôi sẽ bán BTC của chúng tôi và gọi _reset_session(). Nếu không, chúng tôi sẽ đặt phần thưởng vào giá trị ròng hiện tại. Nếu chúng tôi hết tiền, chúng tôi sẽ đặt thực hiện thành 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, {}

Thực hiện hành động giao dịch đơn giản như nhận giá hiện tại, xác định các hành động được thực hiện và số lượng mua hoặc bán.

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

Cuối cùng, theo cùng một phương pháp, chúng tôi sẽ gắn giao dịch với tự giao dịch và cập nhật giá trị ròng và lịch sử tài khoản của chúng tôi.

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 tôi có thể bắt đầu một môi trường mới ngay bây giờ, hoàn thành môi trường dần dần, và thực hiện các hành động ảnh hưởng đến môi trường.

Hãy xem robot của chúng ta giao dịch.

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

Chúng ta sẽ đưa mã vào.StockTrackingGraph.pyBạn có thể lấy mã từ Github của tôi.

Thay đổi đầu tiên chúng ta cần thực hiện là cập nhật self.df ['Date '] thành self.df [Timestamp] và xóa tất cả các cuộc gọi đến date2num, bởi vì ngày của chúng ta đã ở định dạng timestamp unix.

from datetime import datetime

Đầu tiên, nhập thư viện thời gian ngày, và sau đó chúng ta sẽ sử dụng utcfromtimestampmethod để lấy chuỗi UTC từ mỗi dấu thời gian và strftime để nó được định dạng như một chuỗi: định dạng 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]])

Cuối cùng, chúng ta sẽ 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. Sau khi hoàn thành chúng, chúng ta đã sẵn sàng. Trở lại BitcoinTradingEnv của chúng tôi, chúng ta có thể viết các phương pháp hiển thị để hiển thị biểu đồ ngay bây giờ.

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)

Chúng ta có thể xem robot của mình giao dịch Bitcoin ngay bây giờ.

Hình dung robot giao dịch của chúng tôi với Matplotlib

Nhãn màu xanh lá cây đại diện cho việc mua BTC, và nhãn màu đỏ đại diện cho việc bán. Nhãn 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à nhãn ở góc dưới bên phải là giá hiện tại của Bitcoin. Nó đơn giản 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 đào tạo

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

Ví dụ, một hình thức xác thực chéo phổ biến được gọi là xác thực k-fold. Trong xác thực này, bạn chia dữ liệu thành k nhóm bằng nhau, từng nhóm một, riêng biệt, như nhóm thử nghiệm và sử dụng phần còn lại của dữ liệu như nhóm đào tạo. Tuy nhiên, dữ liệu chuỗi thời gian phụ thuộc rất nhiều vào thời gian, có nghĩa là dữ liệu tiếp theo 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, bởi vì robot của chúng tôi sẽ học từ dữ liệu trong tương lai trước khi giao dịch, đó 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ùng một lỗ hổng áp dụng cho hầu hết các chiến lược xác thực chéo khác. Do đó, chúng ta chỉ cần sử dụng một phần số khung dữ liệu hoàn chỉnh làm tập huấn từ số khung đến một số chỉ số tùy ý, và sử dụng phần còn lại của dữ liệu 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ý một số khung dữ liệu, 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)])

Bây giờ, đào tạo mô hình của chúng tôi đơn giản như tạo ra một robot sử dụng môi trường của chúng tôi và gọi 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 các tấm tensor, vì vậy chúng tôi có thể trực quan hóa biểu đồ luồng tensor của mình một cách dễ dàng và xem một số chỉ số định lượng về robot của chúng tôi.

img

Wow, 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 200.000 bước, và phần còn lại sẽ tăng trung bình ít nhất 30 lần!

Lúc đó, 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 đồ phần thưởng mới:

img

Như bạn có thể thấy, một số robot của chúng tôi đang làm tốt, trong khi những người khác đang phá sản. Tuy nhiên, robot có hiệu suất tốt có thể đạt 10 lần hoặc thậm chí 60 lần số dư ban đầu tối đa. Tôi phải thừa nhận rằng tất cả các máy có lợi nhuận đượ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ế cho robot của chúng tôi để kiếm được bất kỳ tiền thật sự. Nhưng ít nhất chúng tôi đã tìm thấy cách!

Hãy thử robot của chúng tôi trong môi trường thử nghiệm (sử dụng dữ liệu mới mà họ chưa bao giờ thấy trước đây) để xem chúng sẽ cư xử như thế nào.

img

Robot được huấn luyện tốt của chúng ta 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 rất nhiều việc phải làm. Bằng cách đơn giản chuyển đổi các mô hình để sử dụng A2C với đường cơ sở ổn định thay vì robot PPO2 hiện tại, chúng ta có thể cải thiện hiệu suất của mình trên bộ dữ liệu này rất nhiều. Cuối cùng, theo đề xuất của Sean OGorman, 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ể thêm phần thưởng vào giá trị ròng, thay vì chỉ nhận ra giá trị ròng cao và ở lại đó.

reward = self.net_worth - prev_net_worth

Hai thay đổi này một mình có thể cải thiện hiệu suất của bộ dữ liệu thử nghiệm một cách đáng kể, và như bạn có thể thấy dưới đây, cuối cùng chúng tôi đã có thể hưởng lợi từ dữ liệu mới không có sẵn trong bộ đào tạo.

img

Nhưng 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 thông số siêu của chúng ta và đào tạo robot của chúng ta trong một thời gian dài hơn.

Trong bài viết tiếp theo, chúng ta sẽ sử dụng tối ưu hóa Bayesian để phân vùng các siêu tham số tốt nhất cho không gian vấn đề của chúng tôi và chuẩn bị cho đào tạo / thử nghiệm trên GPU sử dụ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ăng cường để tạo ra một robot giao dịch Bitcoin có lợi nhuận từ đầu.

  1. Tạo một môi trường giao dịch Bitcoin từ đầu bằng cách sử dụng OpenAI's gym.

  2. Sử dụng Matplotlib để xây dựng hình ảnh của môi trường.

  3. Sử dụng xác thực chéo đơn giản để huấn luyện và thử nghiệm robot của chúng tôi.

  4. Điều chỉnh robot của chúng tôi một chút để đạt được lợi nhuận.

Mặc dù robot giao dịch của chúng tôi không có lợi như chúng tôi đã hy vọng, 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 có thể liên tục đánh bại thị trường. Chúng tôi sẽ xem robot giao dịch của chúng tôi xử lý dữ liệu thời gian thực như thế nào. Xin tiếp tục theo dõi bài viết tiếp theo của tôi và Viva Bitcoin!


Có liên quan

Thêm nữa