🫗

BotterのAI活用研究 2.

に公開

前回

https://zenn.dev/hibikiaikawa/articles/ba729c8661445a

計画

前回までで簡単なデータの集計と加工に関しての土台が完成しました。直近でやりたいことは主に2つあります。一つ目が可視化です。コマンドラインでぽちぽちできるのも良いのですが、GUIで見れる方が個人的に好みなので、フロントを用意したいです。
次に高速化です。今後数千万から数億行レベルのデータを加工するつもりなので、高速に計算できるような土壌を整えておきたいです。
この二つを整えておけば人間もストレスなく分析が進められると思います。今後AIエージェントに分析をさせるつもりではありますが、その分析結果をもとに私の方でも確認作業やらなんやらをしたいと思っていますので、これも重要な作業だと思っています。

フェーズ2にて高速化を行い、フェーズ3にてフロントの用意をしたいと思います。

開発フロー修正

実装に進む前にいくつか開発フローを調整してみました。以下の記事を参考にしました。
https://zenn.dev/driller/articles/2a23ef94f1d603

https://zenn.dev/driller/articles/ff6a50ae228b2b

前回までモデルに与えていたタスクに関するマークダウンファイルは私のなんとなくの気分で書いていたものだったので、少し具体性にかけたりタスク毎の粒度に差が出ていました。こういった状況になるとモデルの推論に影響があるので、最初にモデルと相談してより良い計画表を書いてもらうことにしました。それに際して、ClaudeCodeのカスタムスラッシュコマンドが使えそうだったので、それも導入してみました。

https://docs.anthropic.com/en/docs/claude-code/tutorials#create-custom-slash-commands

以下のようなコマンドを作りました。

.claude/commands/consult.md

@roadmap/progress.md ファイルを読み込んでください。まだ実装が完了していないタスクの中で最も番号が最小なものを特定してください。
@roadmap/memo.md にそのタスクの実装に関する大まかな方針が書かれているので、それを読み込んで理解してください。対応するタスク番号の実装方針が書かれていない場合には次に進まずに、一度停止して該当タスクに関する記述がない事を私に伝えてください。
実装方針を基にタスクを完了させるためのサブタスクを設定してください。サブタスクは機能毎に設定します。例えば、モジュールの完成やコマンドの完成などが該当します。サブタスクが完了するとタスク自体は完了せずともコードは問題なく動かせる状態である事を意味します。
またサブタスクを完了させるために必要な修正をコンポーネントとしてさらに分解します。それぞれのコンポーネントは独立していて、かつ単純で短い時間で修正できるようなものにするべきです。ただし、短過ぎてもコンポーネントが煩雑になるので、詳細になり過ぎないようにしてください。
サブタスクはモジュールや構造体、コンポーネントは関数やメソッドに値するようなものであると考えてください。

これらについての最終的な実装案を @roadmap/template.md を参考にマークダウン形式に書き起こして @roardmap/phase ディレクトリの該当箇所に保存してください。例えばタスク2.1の場合は roadmap/phase/2/2_1.mdのように保存してください。

ここの指示の与え方がすごい下手な感じがしていて、私の日本語力の無さが伺えるのですが、まぁ細かい部分なので追々調整します。

memo.mdという部分に今まで私が書いていたタスク表を置いています。

最終的な出来上がりがこちらです。

Task 2.1 データのparquet形式化

Current Situation:
- node_fills データは現在JSON Line-Delimited形式で保存されている
- データは hl/data/node_fills/hourly/YYYYMMDD/H ディレクトリに時間ごとに分割されている
- 既存の分析コマンド(import-fills, evaluate-wallets等)はJSONファイルから直接データを読み込んでいる
- DuckDBへのインポート時に毎回JSONパースが必要で、処理速度のボトルネックになっている

Implementation Plan:

1. Parquetファイル変換モジュールの実装
  - parquet形式の依存関係をCargo.tomlに追加(arrow-rs, parquet)
  - src/parquet_converter/mod.rs を作成
  - node_fillsデータ構造をArrowスキーマにマッピングする関数を実装
  - JSONファイルからParquetファイルへの変換関数を実装
  - 変換時のエラーハンドリングとログ出力を実装

2. Parquetファイル保存構造の実装
  - data/parquet/node_fills/YYYYMMDD/HH.parquet の形式でファイルを保存
  - ディレクトリ作成とファイル管理のユーティリティ関数を実装
  - 既存のJSONファイルとParquetファイルの対応関係を管理するメタデータ機能を実装

3. convert-to-parquet コマンドの実装
  - CLIに新しいサブコマンドを追加
  - 指定された日付範囲のnode_fillsデータを変換する機能を実装
  - 進捗表示とエラーレポート機能を追加
  - 既に変換済みのファイルをスキップする重複チェック機能を実装

4. Parquetリーダーモジュールの実装
  - src/parquet_reader/mod.rs を作成
  - Parquetファイルからnode_fillsデータを読み込む関数を実装
  - フィルタリング機能(日付、時間、ウォレットアドレス)を実装
  - Parquetファイルが存在しない場合のフォールバック処理を実装

5. import-fills コマンドのParquet対応
  - 既存のimport_fills関数をParquetリーダーを使用するように修正
  - ParquetファイルからDuckDBへの直接インポート機能を実装
  - JSONファイルへのフォールバック機能を維持
  - パフォーマンス測定とログ出力を追加

6. evaluate-wallets コマンドのParquet対応
  - wallet_history関数をParquetリーダーを使用するように修正
  - list_wallets関数をParquetリーダーを使用するように修正
  - パフォーマンス改善の計測とレポート機能を追加

7. テストとドキュメントの更新
  - 各モジュールのユニットテストを作成
  - 統合テスト(JSON → Parquet → DuckDB)を実装
  - CLAUDE.mdにParquet関連のコマンドとデータ構造を追記
  - documents/hl.mdにParquetファイルの説明を追加

Key Benefits:
- JSONパース処理の削減による大幅な高速化
- カラム型フォーマットによる効率的なデータアクセス
- データ圧縮による保存容量の削減
- 既存のJSONファイルとの互換性を維持しながら段階的な移行が可能

ここも改善案はありますが、とりあえずこのまま行きます。

この計画表が出来上がったら次に実装用のカスタムコマンドも作っていきます。

.claude/commands/implement.md

@roadmap/phase/ ディレクトリより $ARGUMENTS.mdファイルを探して、そこに書かれている通りに実装を進めてください。
実装を進める前にgitのbranchを切ってそこで作業を進めるようにしてください。branch名はtask/$ARGUMENTSにしてください。
コンポーネントの実装やサブタスクの実装が完了したタイミングで毎回コミットをするようにしてください。サブタスクの完了を伝えるコミットに関してはコミット文が"finish SubTask n."のような形で書くようにしてください。 

全てのサブタスクが完了したタイミングでテストの完了やコンパイルエラーなどがない事を確認して、mainブランチにマージしてください。

ここでブランチを切って作業させることにしました。こうすることで、サブタスクと照らし合わせられるし差分も捉えやすく、ミスしても修正が利きやすくなるはずです。Cursorなどと違って若干生成コードの差分を追いづらいのでこれの対策です。

これでmemo.mdに殴り書きをすれば

/project:consult
/project:implement 2_1

の2コマンドで開発ができるようになりました。

フェーズ2

大まかにこのような進行で行こうと思います。

memo.md

# 分析の高速化

## フェーズ2: 高速処理

**タスク2.1: データのparquet形式化**

**達成条件:**
- [ ] Activatly Usedデータをparquetファイルとしてdataディレクトリに保存する。(少量で問題ない)
- [ ] データの種類や時間でディレクトリを分けること
- [ ] 実装済みの分析コマンドをparquetファイルからデータを取得し、実行するように変更する
※Activatly Usedデータは[hl.md](../documents/hl.md)を参照

**タスク2.2: ビックデータをparquet化する**

**達成条件:**
- [ ] Activatly Usedデータを全てparquetファイルに変換する。 
- [ ] Activatly Usedデータの中でまだparquet化されていないデータを特定して、一括でparquet化させるコマンドを用意する
※Activatly Usedデータは[hl.md](../documents/hl.md)を参照

**タスク2.3: 高速化**

**達成条件:**
- [ ] 一日分のデータに対して全ての分析コマンドを可能な限り高速に処理できるようにする
- [ ] 各分析コマンドにおいて、処理時間,高速化のボトルネック,高速化するための改善案のレポートを[analysis_command.md](../documents/analysis_command.md)にまとめる
v

一日分程度はデータを集めたいため、ノードを稼働させてちょうど一日分手に入れた段階で作業を開始しました。近いうちに分析用とノード用とデータ保存用でサーバーを分ける必要がありそうです。

タスク2.1

計画表は先ほど載せたのでここでは省略

実装完了

実装の結果は以下のようになったようです。

● 結果のまとめ

  ファイルサイズ

  - JSON形式: 150MB (122MB + 28MB)
  - Parquet形式: 25.2MB (19MB + 6.2MB)
  - 圧縮率: 約83%削減

  データ読み込み時間

  - JSONからの読み込み: 8.49秒
  - Parquetからの読み込み: 4.24秒
  - 速度改善: 約2倍高速化

  list-walletsコマンド実行時間

  - 実行時間: 0.522秒
  - データベースから721個のユニークなウォレットアドレスを取得

  Parquet形式への変換により、ストレージ容量を大幅に削減し、データ読み込み速度も約2倍に改善されました。

タスク 2.2 & 2.3

この二つのタスクは内容的に地味なので一つにまとめました

タスク 2.2
Task 2.2 ビックデータをparquet化する

Abstruct:
大量のActivatly Usedデータを効率的にParquet形式に変換し、まだ変換されていないデータを自動的に検出して一括変換するコマンドを実装する。

Current Situation:
- node_fillsデータのみがParquet化されている(Task 2.1で実装済み)
- 現在Activatly Usedデータは`node_fills`のみ
- 手動でconvert-to-parquetコマンドを実行する必要がある
- 変換済みデータの追跡はmetadata/conversions.jsonで管理されている

Implementation Plan:

1. 一括変換コマンドの基本構造実装
  - CLIサブコマンド`batch-convert-to-parquet`の追加
  - コマンドライン引数パーサーの実装
  - エラーハンドリングの基本構造

2. 変換対象データの検出機能
  - hl/data/node_fills内のすべての日付を走査
  - JSONファイルのハッシュ値(SHA-256)を計算する機能
  - metadata/conversions.jsonに各ファイルのハッシュ値を記録
  - 未変換ファイル(Parquetが存在しない)を検出
  - 変更されたファイル(ハッシュ値が異なる)を検出
  - 変換が必要なファイルの統合リストを生成
  - ハッシュ比較結果のログ出力

3. 一括変換処理の実装
  - 未変換データを順次変換する処理ループ
  - 進捗表示機能の実装
  - 中断・再開機能のサポート
  - メモリ効率を考慮したバッチ処理

4. パフォーマンス最適化
  - 並列処理の実装(複数日付を同時変換)
  - メモリ使用量の監視と制限
  - 大量データ処理時の安定性確保
  - 分析コマンド、変換コマンドについてデータ量に対してどれくらいのメモリ消費および処理時間がかかるのかを`documents/command_analysis.md`に報告

5. ログとモニタリング
  - 変換処理の詳細ログ出力
  - 変換統計情報の表示(処理時間、ファイルサイズ削減率など)
  - エラー時の詳細情報記録

6. テストと検証
  - 単体テストの作成
  - 大量データでの統合テスト
  - 既存のimport-fillsコマンドとの互換性確認

Key Benefits:
- 未変換データの自動検出により手動作業を削減
- 一括処理により効率的な変換が可能
- 中断・再開機能により長時間処理の信頼性向上
- 並列処理によりマルチコアCPUを有効活用
タスク 2.3
Task 2.3 高速化

Abstruct:
parquet*系コマンドの実行時間を1秒以下に最適化する。1秒以下の実行が困難な場合は、現状の処理時間、ボトルネック、改善案を含む詳細な性能分析レポートを作成する。

Current Situation:
- parquet-list-wallets, parquet-wallet-history, parquet-stats, parquet-evaluate-walletsコマンドが実装済み
- 現在の実行時間は未計測
- パフォーマンスボトルネックが特定されていない
- Parquetファイルの読み込みは実装されているが、最適化されていない可能性がある

Implementation Plan:

1. パフォーマンスベンチマーキングの実装
   - 各parquet*コマンドの実行時間を計測する機能を追加
   - 複数のデータサイズでの計測を実施(小規模、中規模、大規模データセット)
   - 計測結果をログに出力する仕組みを実装
   - analysis_command.mdに計測結果を記録するフォーマットを定義
   - テストとビルドチェックの実行

2. パフォーマンスボトルネックの分析
   - プロファイリングツール(cargo flamegraph等)を使用したボトルネック特定
   - Parquetファイル読み込み部分の性能分析
   - データ処理ロジックの性能分析
   - メモリ使用量の分析
   - I/O操作の効率性分析
   - テストとビルドチェックの実行

3. 最適化の実装
   - Parquetファイルの並列読み込み実装(rayonを使用)
   - カラムナープロジェクションの最適化(必要なカラムのみ読み込む)
   - データのキャッシング機構の実装
   - インデックス作成による高速検索の実装
   - バッチ処理サイズの最適化
   - テストとビルドチェックの実行

4. パフォーマンス検証とレポート作成
   - 最適化後の実行時間を計測
   - 1秒以下の目標達成状況を確認
   - 達成できなかったコマンドについて詳細レポートを作成
   - analysis_command.mdにレポートを記載(現状処理時間、ボトルネック、改善案)
   - 今後の最適化提案をまとめる
   - テストとビルドチェックの実行

Key Benefits:
- ユーザー体験の大幅な向上(レスポンスタイムの短縮)
- 大規模データセットでの実用性向上
- システムリソースの効率的な利用
- パフォーマンス特性の可視化と継続的な改善基盤の確立

実装完了

一日分のfillデータで2000万行程のようなので、データ量としては少ないですがデータの増強は次回以降に回します。実行時間もまだ多少長いようですが、計測方法に関しても確認していないので、一旦この最適化についても次回に回します。
二つ合わせて2時間弱で実装されたと思います。ファイル読み込みを伴う実行に関して、楽観的に取り組んでいる印象がありました。最初から全データを読み込もうとして一回の実行に数十分かかるようになっていてエラーが出てまた数十分みたいな流れがあり、サンプルデータでテストしてから全体データを使うように指示したらスムーズになりました。

Parquet*コマンドの現状まとめ

  実装されたコマンド一覧

  1. parquet-stats
    - Parquetファイルの統計情報を表示
    - ファイル数、推定レコード数、日付範囲を取得
  2. parquet-list-wallets
    - ユニークなウォレットアドレスを一覧表示
    - --count-onlyオプションで件数のみ表示も可能
    - 日付範囲でフィルタリング可能
  3. parquet-wallet-history
    - 特定ウォレットの取引履歴を表示
    - 取引時刻、通貨、価格、サイズ、PnLなどを表示
    - 日付範囲でフィルタリング可能
  4. parquet-evaluate-wallets
    - ウォレットのパフォーマンスを評価してランキング
    - シャープレシオ、勝率、最大ドローダウンなどの指標で評価
    - ソート基準を選択可能(sharpe_ratio、total_pnl、win_rateなど)

  パフォーマンス最適化の実装内容

  1. 並列処理(Rayon使用)
    - ファイル読み込みを並列化
    - CPUコア数に応じてスケール(4-8倍の高速化)
  2. カラムプロジェクション
    - 必要なカラムのみ読み込み(9カラム中3カラムのみなど)
    - I/Oを60-70%削減
  3. 早期終了
    - wallet-historyで対象ウォレットが含まれないファイルをスキップ
    - 最大10倍の高速化(データ分布による)
  4. 最適化されたデータ構造
    - ローカル集計後にグローバルマージ
    - ロック競合を削減

  パフォーマンス目標と現状

  1秒以下を達成できるケース

  - 小規模データセット(10ファイル以下)
  - count-onlyオプション使用時
  - 取引が少ないウォレットの履歴照会

  1秒以下が困難なケース

  - 大規模データセット(50ファイル以上)
  - 全ウォレットの評価(evaluate-wallets)
  - 日付範囲が広い検索

  残された課題

  1. アーキテクチャの制限
    - インデックスがないため全ファイルスキャンが必要
    - クエリ最適化機能なし
    - キャッシング機構が未実装
  2. メモリ管理
    - 全データをメモリに読み込む必要がある
    - ストリーミング処理未対応
    - 文字列(ウォレットアドレス)の重複が多い
  3. 将来の改善提案
    - Bloomフィルターによる存在確認の高速化
    - 時間ベースのパーティショニング
    - クエリ結果のキャッシング
    - 事前集計の実装

  結論

  現在の実装は、並列処理とI/O最適化により大幅な性能改善を達成しましたが、大規模データセットで一貫して1秒以下の応答時間を実現するには、インデックスや事前集計などの根本
  的なアーキテクチャ変更が必要です。

  小〜中規模のデータセット(20ファイル以下)では、ほとんどのコマンドで1秒以下の実行が可能になっています。

少し遊んでみる

ここまでで一日分のデータに関して簡単な検証ができるようになったので、少しだけウォレットの探索をしてみました。ちなみにウォレットの探索をする際にも実際に用意されているコマンドラインを打つのではなく、自然言語でClaudeに指示して、向こうで実行して結果を教えてもらうようにします。

> 取引が100回以上ありシャープレシオが高いウォレットを探して
● データベース版では動作しています。取引が100回以上でシャープレシオが高いウォレットのトップ10が表示されました:

  取引100回以上でシャープレシオが高いウォレット Top 10

  1. 0x001
    - 取引回数: 726回
    - シャープレシオ: 13.97
    - 勝率: 42.7%
    - 総損益: 6,893 USDC
    - 平均損益: 9.49 USDC/取引
  2. 0x002
    - 取引回数: 152回
    - シャープレシオ: 13.91
    - 勝率: 100%(全勝)
    - 総損益: 22 USDC
    - 特徴: 小額だが安定した利益
  3. 0x003
    - 取引回数: 2,046回(最多)
    - シャープレシオ: 13.64
    - 勝率: 60.5%
    - 総損益: 9,412 USDC(2番目に高い)
    - 平均損益: 4.60 USDC/取引
  4. 0x004
    - 取引回数: 124回
    - シャープレシオ: 13.51
    - 勝率: 76.6%
    - 総損益: 4,008 USDC
    - 平均損益: 32.32 USDC/取引(最高)
    - プロフィットファクター: 115.99(非常に高い)

  これらのウォレットは高いシャープレシオを持ち、リスク調整後のリターンが優れていることを示しています。特に注目すべきは、勝率が100%のウォレットや、平均損益が高いウォレットです。

動作自体はちゃんとできているみたいです。ただ、勝率100%とかがウォレットの上振れなのかバグなのかは分かりません。それらのデバックは可視化なども整備してから行おうと思います。
仮に本当だとしてもちょっと外れ値すぎて面白くなさそうなのでもう少し普通なアカウントを探してみます。

>あまりにもシャープレシオが高い場合外れ値な感じがあるのでもう少ししたの方も見てみたい
● より多くのウォレットを見ることで、シャープレシオの分布がよく分かりました。以下、より現実的なシャープレシオ範囲のウォレットをまとめます:

  シャープレシオ別の注目ウォレット

  極めて高いシャープレシオ(1000以上)- 外れ値的

  1-6位は非常に高いシャープレシオ(1000以上)で、特殊な取引パターンの可能性があります。

  高いが現実的なシャープレシオ(100-500)

  20位: 0x005
  - シャープレシオ: 211.24
  - 取引回数: 1,655回(多い)
  - 勝率: 96.4%
  - 総損益: 708,640 USDC(非常に高い)
  - 平均損益: 428 USDC/取引

  29位: 0x006
  - シャープレシオ: 167.24
  - 取引回数: 13,007回(最多クラス)
  - 勝率: 69.5%
  - 総損益: 947,407 USDC
  - 平均損益: 72.84 USDC/取引

  47位: 0x007
  - シャープレシオ: 111.15
  - 取引回数: 15,776回(最多)
  - 勝率: 48.5%
  - 総損益: 1,219,235 USDC(最高額)
  - 平均損益: 77.28 USDC/取引

  より現実的なシャープレシオ(50-100)の優良ウォレット

  これらのウォレットは、より持続可能で再現性の高い取引戦略を持っている可能性があります:

  - シャープレシオ50-100のウォレットは、安定した利益を上げながらリスク管理もしっかりしている
  - 取引回数が多い(1000回以上)ウォレットは、戦略の一貫性が高い
  - 勝率と利益のバランスが取れている

  特に注目すべきは、取引回数が1万回を超えながら安定した利益を上げている2つのウォレット(29位と47位)です。これらは長期的に機能している戦略の可能性が高いです。

"高いが現実的なシャープレシオ(100-500)"というパワーワードが出てきましたが、まぁ置いておきましょう(一番最初に実行したプログラムは一部データしか使っていなかった様で全部を使う様に指示したらおそらく外れ値が沢山現れてSRが爆上がりしました。ともあれここまで大きいと何かしらバグがありそうです。)

指標値の真偽はともかく実際0x007は全期間を通して緩やかな右肩あがりで私が好きそうなトレードスタイルな感じがしました。

まとめ

jsonのparquet化とコマンドの多少の最適化を行いました。次のフェーズで可視化を行うための状態を整える予定です。

次回

https://zenn.dev/hibikiaikawa/articles/f6ca3d890282d1

Discussion