クラウド上のサーバーレスのbotを支えるサービス

これは、[仮想通貨botter Advent Calendar 2024](https://qiita.com/advent-calendar/2024/botter) シリーズ2 9日目の記事です。

今までこんな感じのことをやっている人間です。

Python歴：　6年
仮想通貨トレード歴：　7年
クラウド利用歴：　7年
Botter歴：　2024年3月時点で **0年!**

「おいおい、そのスキルセットがあるのにまだ裁量トレードで消耗してるの？」
そんな煽りが脳内を駆け巡り、ついに私もBotterデビューを決意しました。

すでに有名なbotter先輩方が、Botterの秘伝や構成例を公開されているのを参考にしましたが、難しい界隈用語に初見殺されてちんぷんかんぷん。。
また、探し方が悪いからなのかニッチだからなのか、MLや売買ロジックの外側の部分、特にマイクロサービスを中心に構成するとしたらどうするのか、という例が多くないと感じています。
「なら、とりあえず1つでも公開してみるか」と動き始めた次第です。

職業柄、クラウドITインフラエンジニアなので、クラウドサービスの特性を活かした構成は経験しています。でもこれで、**この構成でつよつよBotが作れるわけじゃない**ので、そこの誤解はナシで！お願いします。

:::note info
「DEX Bot？なにそれ美味しいの？（旨味はあるんだろうけれど）」という状態なので、CEX Botです。あとエッジ狙いとか適正レートへの貢献とかじゃなくて、低頻度botです。
まずは自分がやりたいように形にすることが重要ですよね！
:::

# どのクラウドサービスで動かすのか

普段はAWS使いですが、どうせなら **AWS, Azure, GoogleCloud** の3大クラウド全部盛りでBotを動かしてみます。

クラウド環境でBotを動かすサービスは主に3つ考えられます。

-  **IaaS** (仮想マシン) 上に直接構築：
  自由度が高くOSレベルで何でもできる。独自のスケジューラーや通知システムを作りやすい。手間もコストもそれなり。
-  **Dockerコンテナ** によるデプロイ：
  ローカル環境で作業したコンテナをそのままクラウドにデプロイできる。「ローカルで動いたのにクラウドでコケる」問題を回避できるのがデカい。
- **FaaS** (Function as a Service) によるサーバーレス構成：
  **低コストで安い。** Botのプログラムを関数単位でデプロイ可能。 **そして安い。** コードに集中できる。 **そして安い。**

低コストで始めるならFaaS一択でしょう。
*「え？コスト？そんなのBotの利益で吹き飛ばせるだろ」* 
とツッコミが来そうですが、少しでも安く抑えられるのはメリットです。

3大クラウドのFaaSはこれです。

- AWS：AWS Lambda
- Azure：Azure Functions
- Google Cloud：Cloud Run functions

今回はFaaS＋周辺のサーバーレスサービス（イベントトリガー、ストレージなど）と連携した構成で進めてみます。

# どういうbotか

今回作ったのは、特定の通貨ペア（例：BTC/USDT）で、売買シグナルを拾って自動エントリーする、量産型初心者向けBotです。
以下の4つの機能をモジュール化して実装しています。

- ローソク足データを記録する機能
- 売買シグナルを基にトレードする機能
- MLによるパラメーター最適化機能
- データ参照用Web機能

## 全体構成図

上述した全ての機能を1枚で表した全体構成図です。

まずは、特定のクラウド事業者に限定しない形で図にしています。

![crypto-botter-serverless-Common.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204490/fddad29f-864b-b449-4f12-f21555595d5d.png)

各クラウド事業者の共通項の表現をしようとすると、なんだかぼやけてしまいますね。
イメージしにくいので、AWSの場合で示してみます。

![crypto-botter-serverless-AWS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204490/ffd1d633-5ccc-ba12-ba96-354e78f74315.png)

これで分かりやすくなったでしょうか。

- タイマー → 　Amazon EventBridge
- ワークフロー → 　Step Functions
- オブジェクトストレージ → 　Amazon S3
- セキュアデータ保管 → 　Systems Manager Parameter Store
- Key-Value DB → 　Amazon DynamoDB
- メッセージ通知 → 　Amazon Simple Notification Service (SNS)
- CDN → 　Amazon CloudFront

設計のポイントがいくつかありますが、その中でも **関数の粒度** と **データの配置** は大事ですので、この2点にフォーカスします。

### 関数の粒度

1つの関数に機能を詰め込みすぎると、実行時間オーバーのリスクがあったり、コードのメンテナンス性が悪化したりします。かといって細かく分けすぎると、パフォーマンスのオーバーヘッドや、関数間の連携のロジックが逆に複雑化するなどの懸念も。

今回の例では、主要な処理を5つの関数に分けています。

1. ```update-price-dataset```： ローソク足データの更新
2. ```signal```： シグナルの生成
3. ```position-control```： ポジション管理と注文処理の呼び出し
4. ```order```： 注文処理
5. ```update-params```： パラメータの最適化

一旦はこの単位で作成していますが、複数の取引所間の注文があるなら ```order``` をさらに細分化するのが良さげ。機械学習を強化するなら ```update-params``` を細分化するのもありです。

### データの配置

データの扱いをナメると痛い目を見るのがBot界隈です。「APIキー漏洩で資産蒸発」なんて話も聞きます。今回は以下の構成でデータを管理しています。

- シークレットデータ保管： 注文APIキーを保管
- Key-Valueデータベース（NoSQL DB）： 学習済みパラメーターを保管
- オブジェクトストレージ： ローソク足データやポジション状態データを保管

ポジション状態データは、静的Webサイトホスティングで外部から参照したいので、オブジェクトストレージに置いてます。ローソク足データは、Key-Valueデータベースの方が良いのかも。

## 動作1　ローソク足データを記録する機能

![crypto-botter-serverless-flow1_cu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204490/9b0d828a-2e63-b43d-a47d-f99d65394e20.png)

赤枠の部分を説明します。
ローソク足データを記録する機能です。いわゆる自炊です。

1. タイマートリガーで定期的にワークフローを呼び出します。1分足、5分足などに応じたインターバルで実行します。
2. ワークフローには、3つの関数 ```update-price-dataset```、```signal``` 、```position-control``` を順に登録しています。
3. 関数#1 ```update-price-dataset```
取引所のPublic APIから最新価格データを取得します。オブジェクトストレージ（バケット／BLOB）に格納されているロウソク足のデータを読み込み、最新の値を追記して、再びオブジェクトストレージに保管します。
4. オブジェクトストレージに格納するファイルは、バックテスト用に扱いやすい形で整えます。

## 動作2　売買シグナルを基にトレードする機能

![crypto-botter-serverless-flow2_cu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204490/357f976f-ad6b-e0c9-9cd5-e653e0b425ff.png)

赤枠部分を説明します。（動作1の赤枠とは囲んだ位置が違います）
ここがトレードの本番の機能。シグナルに基づいてポジションを操作します。

1. 関数#2 ```signal```
売買ロジックの中核。機械学習で最適化されたパラメータを活用し、ローソク足データを分析します。
出力されるシグナルは、5種類です。
    - **BB** (強い買いシグナル)
    - **B** (買いシグナル)
    - **Y** (シグナル無し)
    - **S** (売りシグナル)
    - **SS** (強い売りシグナル)
2. 関数#3 ```position-control```
  現在のポジション（例: 保有中のロング・ショート量）と最新のシグナルを元にオーダーを決定します。
  この部分は、シグナル **Y** なら全力ロングに対する75%を超過する量を決済、等のように、ポジションサイズ調整アルゴリズムに依存するため、戦略の優劣を左右します。
ワークフローのサービスでは、関数#2 の出力データを 関数#3 の入力データとして渡すことができるので、5種類のシグナルのうち1つを渡しています。
3. 関数#4 ```order```
  最終的に取引所のPrivate APIを叩き、注文を実行します。この関数は ```position-control``` 関数内から直接APIで実行しています。

取引所にもよりますが、例えば 5 ETH のショートポジションの状態で 8 ETH を購入する場合、5 ETH のショート決済と、3 ETH のロング購入の2回API実行が必要になったりします。そのため```order```関数は、ワークフローで定義しておらず、関数#3 ```position-control``` のコードから呼び出すようにしています。
注文用のPrivate APIキーは、安全のためシークレット保管のサービスを利用します。
注文結果は、Slackやメール通知サービスでリアルタイムにレポートします。

## 動作3　MLによるパラメーター最適化機能

![crypto-botter-serverless-flow3_cu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204490/58fdd4bb-d9d8-7a5c-d4f5-b992a8376872.png)

Botの肝となる、機械学習(ML)によるパラメータチューニングを自動化した機能です。これが戦略の成否を大きく左右します。

1. もう1つのタイマートリガーです。機械学習を頻繁に回すと過学習や無駄なリソース消費に繋がります。
そのため、週足単位の更新頻度（例: 毎週日曜深夜）に設定しました。
2. 関数#5 ```update-params```
機械学習を用いて、戦略ロジックに用いるパラメーターを最適化します。パラメーターとは、例えば移動平均の期間 m, n やRSI閾値などです。学習済みの結果はKey-Value DBに書き込み、リアルタイム戦略で利用します。

注意点として、クラウドFaaSの実行時間には上限があります。（例: AWS Lambdaなら最大15分）その値を超えないように学習時間を調整しました。

## 動作4　データ参照用Web機能

![crypto-botter-serverless-flow4_cu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204490/4daefb17-ce16-3fad-f12d-0a8d94f02a42.png)

スマホやPCから、Botの稼働状況を確認できるWeb UIを提供します。
外出先でスマホからサクッと確認できるのが、この機能のメリットです。
CDNを使わずとも実装可能です。
AWSの場合は、AWS Amplifyを使うと、CDN＋静的Webサイトホスティングをワンストップで構築することもできます。

Webは参照だけを実装しましtが、中断・再開・ロジックの切替、などの更新機能を実装すれば、より便利になるでしょう。

# Azureの場合

![crypto-botter-serverless-Azure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204490/ea795371-3c55-ee6e-378c-12daa2833261.png)

こちらはAzureで実装した場合のアーキテクチャ例です。

- タイマー → 　アプリのタイマートリガー機能
- ワークフロー → 　ロジックアプリ(logic apps)
- オブジェクトストレージ → 　Blob Storage
- セキュアデータ保管 → 　Key Vault
- Key-Value DB → 　Cosmos DB
- メッセージ通知 → 　関数アプリからSlackのWebHookやSendgrid
- CDN → 　Cloud CDN

構築時に、Azure Functions周りの権限エラーが多発したため、それに何度も泣かされ心が折れそうになりました。
その結果、正直なところ今回の構成は部分的にしか完成しておらず、フル実装の実績はありません。ただ、理論上は正しく動作するはずです（たぶん！）。

# Google Cloudの場合

![crypto-botter-serverless-GoogleCloud.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204490/a1abbcc3-ea1a-403b-aecf-01c67bca5989.png)

こちらはGoogle Cloudを使った構成例です。

- タイマー → 　Cloud Scheduler
- ワークフロー → 　Workflow
- オブジェクトストレージ → 　Cloud Storage
- セキュアデータ保管 → 　Secret Manager
- Key-Value DB → 　Firestore
- メッセージ通知 → 　Pub/Sub
- CDN → 　Cloud CDN

こちらもまだ途中までの実装のみで、本格運用まで持っていっていませんが、パーツ単位では動作するので、おそらくこの構成で動作するはずです（きっと！）。

# 比較してみた感想

AWSが一番構築しやすい印象でした。（普段から使い慣れているからというのが大きい。）
AWSは特に以下の点が良かったです。
- デプロイ爆速：
AWS Lambdaは、関数の再デプロイが10秒もかかりません。「コード修正→即反映」のスムーズさは最高です。Azureはデプロイが劇遅なので、もう少し早いといいなぁ。。
- 失敗率の低さ：
AWSでは、クラウド基盤側の原因で関数実行が失敗することは、何カ月回しても0回。この安定感はさすがクラウド界のレジェンドという感じです。
- IPの信頼性：
関数から取引所APIへのアクセスで、AWSでは特に問題なかったですが、Google Cloudではいくつかの取引所でリクエストが弾かれました。IPの信頼性か。。

Google Cloudには、Cloud Runコンテナをという強みもありますし、AzureにはAzureの利点もあります。
視点を変えて各クラウドの特徴をもう少し生かすと、評価も変わるかもしれません。

# 利益は出たのか

**「ガチホ(HODL)勢にはぜひ勝ちたい！」** と強くライバル心を燃やして、HODLに勝つためのML戦略をバックテストして初期パラメータを決めて、実践投入しました。

2024/4/10から2024/11/30までの結果は、、、

|比較|2024/4/10時点|2024/11/30時点|増加率|
|--|--|--|--|
|HODLだった場合|1BTC=70612 USD|1BTC=90716 USD|**28.47**%|
|botの運用結果|USD評価額 100 (とする)|USD評価額 107.66|**7.66**%|

なんと、HODLにぼろ負けじゃないか・・・ビギナーでも勝ったよって記事にしたかったのに、残念！！まあそんなにうまくいくわけないよね。
利益出ているんだし、いいか。

# おわりに

この構成例を見て、
*「俺もBotterデビューして爆益になるぞ！」*
と思って邁進してくれるとうれしいです。
・・・
・・・嘘です。そんなこと思っていません。
**自己責任** の一言に尽きます。この沼は甘くないし、爆益の裏には爆損が潜む世界です。でも、それでも挑戦してみたいと思うなら、~~どうぞご勝手に~~一緒に進んでいきましょう。

*シンプルな低頻度botならこんな構成になるよね、なにをダラダラと書いてあるんだこの記事は*
って突っ込まれる内容になってないだろうか。
まあいいか。

もう少し本腰を入れて、まともなbotを作れるよう、情報をキャッチアップして取り組もうかな。