2023.01.12

強化学習でマーケットメーキングしてみる

はじめに

みなさんこんにちは、グループ研究開発本部 AI研究開発室のK.Fです。今回は、前々回の記事の続きで、強化学習を用いたマーケットメーキングの実装編になります。

実際に、暗号資産取引所のマーケットデータを取得し、強化学習モデルのマーケット戦略の取引実績をバックテストしていこうと思います。

マーケットメーキングおさらい

前々回の記事から引用

マーケットメーキングの取引戦略は、mid priceを基準として、その付近の上下に指値を出し、そのスプレッド分の利益を得るというものです。上下に出した指値に対して、価格が不利な向きに動いてしまうと、片方だけ約定してしまう、または、両方とも約定しない(在庫を抱える)ことになります。

結論

  1. マーケットメーキングのための強化学習モデルを実装し、実際の取引データでバックテストを行いました。
  2. 実装にあたって、バックテストのロジック周り、損益計算を自前で実装しました。
  3. 結果は収益をあげることができませんでしたが、多くの改善ポイントも探ることができました。

強化学習モデル

以前の記事でマーケットメイキングのための強化学習モデルの環境・行動・状態・報酬について説明しました。今回は、GMOコインPublic APIから実際に取得したデータをもとに、環境・行動・状態を扱いやすい形で再定義します。

環境: Environment

Public APIから取得した約定履歴のデータはsymbol: マーケットの種類(BTC), side: 売買種別(BUY/SELL), size: 注文量, price: 価格, timestamp: 約定時刻 の5つの情報をカラムに持っています。これを時間的に集約し集計することでOHLCVなど一般的な指標に変換し環境を定義します。時間的に集約するためのイベント分割の間隔の決め方は複数ありますが、mid price (中値)の時間変化が一定量を超えた間隔で分割する、価格変動ベースイベントを用います。具体的には、一つ前のmidpointからしきい値割合(MIDPOINT_CHANGE_RATIO_TH)以上に変化した場合にイベントを分割します。そして、そのイベント単位でOHLCVなどの指標を計算し、バックテストを行います。

イベント分割の実装例は以下のようになります。

"""
  midpoints: List[float]
"""
MIDPOINT_CHANGE_RATIO_TH = 0.01 # 1%
pre_midpoint = midpoint[0]
event_separate_idx = []
for i, midpoint in enumerete(midpoints, start=1):
  if midpoint <= pre_midpoint * (1 - MIDPOINT_CHANGE_RATIO_TH) \
    or midpoint => pre_midpoint * (1 + MIDPOINT_CHANGE_RATIO_TH):
      event_separate_idx.append(i)
  pre_midpoint = midpoint

行動: Action

簡単化のために、行動空間を以下のように定義しなおしました。注文のサイズはすべて同じとします。


action_id details
0 何もしない
1 指値買い and 指値売り (mid pointから上下に指す)
2 成行買い or 成行売り(在庫処分)

行動は、何もしない、mid pointの上下に両張りで指値を出す、在庫をすべて成行で売るの三通りしかありません。mid pointからの上下の振れ幅は自由に決めれますが、今回は簡単化のために固定値としています。

状態: State

状態は前回の記事4.4節で定義したものと同一です。

報酬: Reward

バックテストを行って得た収益をそのまま報酬にします。

マーケットから取得したデータを見てみる

実際に、Public APIを用いて取得したデータを用いて、mid pointが0.01%以上、上下に振れる点をプロットしたものを下図に示します。

Fig1. mid price

データは2022/9/1から2022/12/31までのものを利用しています。図より、sideがbuyまたはsellのeventsが約57万あるのに対して、分割後のイベントは約360件と約0.06%に減少しています。

バックテストと損益計算

マーケットメーキングでは、mid priceの上下に指値を出すため、上下の振れ幅の割合をENTORY_POINTとすると、売値と買値はそれぞれ以下のように計算することができます。

ENTORY_POINT = 0.01 # mid priceの1%上下に指値を出す
sell_price = pre_midpoint * (1 + ENTRY_POINT)
buy_price = pre_midpoint * (1 - ENTRY_POINT)

指値の損益計算

上下の指値が約定するかの判定は、次の足のhigh/lowがその価格を抜いているかで判定します。計算式は以下のようになります。

"""
" 在庫を保存するクラス
" 在庫が発生したときのpriceを格納する (sizeはすべて同じなので情報として保存しない)
inventory = Inventory(long=[], short=[])
"""

MAKER_COMMISSION = 0.01 # makerの手数料

if state.low < buy_price and state.high > sell_price:
  # 上下の指値が約定
  profit = sell_price * (1 - MAKER_COMMISSION) - buy_price * (1 + MAKER_COMMISSION)
else:
  if state.low < buy_price:
    # 下の買値のみ約定
    profit = buy_price * (1 + MAKER_COMMISSION)
  else:
    # 買値が約定せず
    inventory.long.append(buy_price)
  if state.high > sell_price:
    # 上の売値のみ約定
    profit = -1 * sell_price * (1 - MAKER_COMMISSION)
  else:
    # 売値が約定せず
    inventory.short.append(sell_price)

成行の損益計算

成行の約定金額は、次の足のopen値で決まるので、計算式は以下のようになります。
TAKER_COMMISSION = 0.05 # takerの手数料

if len(inventory.long) != 0:
  # longの在庫処分
  # profitはopen値と平均取得金額の差額にsize(inventory.longの長さ)をかけたものから、手数料を引くと求まる
  long_profit = (state.open - np.mean(inventory.long)) * \
    len(inventory.long) - TAKER_COMMISSION * state.open
  profit = long_profit
  inventory.long.clear()
if len(inventory.short) != 0:
  # shortの在庫処分
  # profitは平均取得金額とopen値の差額にsize(inventory.shortの長さ)をかけたものから、手数料を引くと求まる
  short_profit = (np.mean(inventory.short) - state.open) * \
    len(inventory.short) - TAKER_COMMISSION * state.open
  profit = short_profit
  inventory.short.clear()

実験結果と考察

randomに行動を選択した場合とDQNを用いて行動選択した場合を実装しました。2022/9/1~2022/12/31の期間のデータを50%学習、50%テストで分割し、それぞれのエージェントで学習時のprofitとテスト時のprofitを以下の図に示します。

Fig2.1. train profit

Fig2.2. test profit

図より、ランダムに行動を選択した場合は学習・テスト時どちらもprofitが安定せず、プラスになったりマイナスになったりを繰り返していることが分かります。つまり、ランダムに行動を選択する場合、収益をあげることが難しいことがわかります。また、DQNを用いて行動選択した場合は常に、profitが0になっていることが分かります。学習時も早期に学習が終了してしまい、何もしない 0 が最善の行動であると学習してしまったようです。

DQNはチューニング可能なハイパーパラメータが複数あるので、もう少し調整次第で収益を改善することができるかもしれません。また、今回はDQNを用いていましたが、前回の記事で紹介したActor-Criticベースのモデルやその他の強化学習モデルを試すと、収益を改善できる可能性もあります。

まとめ

マーケットメーキングのための強化学習モデルを実装し、実際の取引データでバックテストを行いました。実装にあたって、バックテストのロジック周り、損益計算を自前で実装しました。結果は収益をあげることができませんでしたが、多くの改善ポイントも探ることができました。

最後に

グループ研究開発本部 AI研究開発室では、データサイエンティスト/機械学習エンジニアを募集しています。ビッグデータの解析業務などAI研究開発室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ募集要項一覧からご応募をお願いします。 一緒に勉強しながら楽しく働きたい方のご応募をお待ちしております。

  • Twitter
  • Facebook
  • はてなブックマークに追加

グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。

 
  • AI研究開発室
  • 大阪研究開発グループ

関連記事