1. はじめに:プロジェクトの再編からスタート
仮想通貨Botの運用に向けて、今回はプロジェクト構造の再編から着手した。
その理由は単純で、「機能が増えてきた分、構成が煩雑になってきた」からだ。
特に次のような課題が浮かび上がっていた:
- スクリプト間の依存関係がコードを追わないと分からない
- 各種ファイル(設定・ログ・DBなど)の保存場所が散らかっている
- Dockerコンテナとホストマシンの間でファイルパスが不安定
こうした状態では、本番運用への移行やCI/CDの自動化、監視体制の構築が足を引っ張られるのは目に見えていた。
構造が崩れていた例
たとえば、起動フローを見てもその断片性は明らかだった。
entrypoint.sh → envsubst で config を生成 → mmbot.cli が config.json を読む
一見シンプルに見えるが、
- config のテンプレートがどこにあるか
- JSON の生成先がどこか
- cli 側でどこを参照しているか
がすべて ハードコードされており、実行環境ごとに都度確認が必要だった。
再編のスタート地点:構造の「見える化」
そこでまず私が行ったのは、プロジェクトの構造を手書きで可視化することだった。
以下は、当初の再編着手時に書いた構造メモを再現したものである。
MMBot/ ├─ compose/ ← Compose YAML、Dockerfile、entrypoint.sh ├─ env/ ← .env.testnet / .env.production ├─ src/ │ └─ mmbot/ │ ├─ cli.py ← メインロジック │ ├─ logging_setup.py │ └─ guard.py / execution.py / notify.py … ├─ config/ │ └─ mmbot/ │ ├─ testnet/mmbot.tpl │ └─ mainnet/mmbot.tpl └─ data_host/ ← ホスト側でマウント予定のログ・DB保存先
この構造を描きながら「どのスクリプトがどの環境変数を必要としているのか?」「ファイルの出力先はどこにしたいのか?」を洗い出した。
結果として、以下のような狙いが明確になった:
CONFIG_DIR
/DATA_DIR
を環境変数化し、ホスト⇄コンテナ間で柔軟に切り替える- 起動フローの中でどこで何が生成されるかを固定化し、安全にトレースできる構造にする
.env
の役割を明確に分離し、「Compose用」「Bot内用」を整理する
こうして構造を「見える化」したことで、
このあと進めていく修正・リファクタ・テストが驚くほどスムーズに進行していった。
思い返せば、この手書き構造図こそが開発加速のトリガーだったとさえ思う。
このあとのセクションでは、実際にどのように環境変数化やDocker構成の調整を進めていったかを段階的に振り返っていく。
2. CONFIG_DIR / DATA_DIR の環境変数化
構造を見える化したことでまず最初に明確になったのは、多くのパスがハードコードされていたという事実だった。
典型的なのは以下のようなコード:
# entrypoint.sh(修正前) envsubst < /app/config/mmbot/testnet/mmbot.tpl > /app/config_testnet.json
# cli.py(修正前) cfg_path = Path("/app/config_testnet.json") db_file = Path(f"mmtrades_{mode}_{date}.db")
このようなパスは、ローカル開発・テストネット・本番環境で全て異なる要件があるのに、書き換える術がない。
また、出力ファイルの保存先(ログ、DB)も同様に /app/data
に固定されており、ホスト側から直接見たり、ログを解析したりするには不便だった。
対応方針:「すべては環境変数で切り替える」
そこで取った方針はシンプルかつ強力なものである。
CONFIG_DIR / DATA_DIR を環境変数にし、全てのパスをこれに従って動的に決定する。
この設計によって、以下のような状況に柔軟に対応できるようになる:
ケース | 旧 | 新 |
---|---|---|
テスト環境のconfigを読みたい | 固定パスを書き換える | MMBOT_MODE=testnet + CONFIG_DIR=/app/config |
ホスト側にログ/DBを書きたい | docker cp で毎回コピー | DATA_DIR=$PWD/data_host でマウント可能 |
本番とtestnetでパスを分けたい | entrypointを分岐させる必要あり | .env.production / .env.testnet に値を書くだけ |
実装内容(修正例)
🔧 entrypoint.sh
CONFIG_DIR=${CONFIG_DIR:-/app/config} DATA_DIR=${DATA_DIR:-/app/data} CFG_TPL="${CONFIG_DIR}/mmbot/${MODE}/mmbot.tpl" CFG_JSON="/app/config_${MODE}.json" envsubst < "${CFG_TPL}" > "${CFG_JSON}"
🔧 cli.py
CONFIG_DIR = Path(os.getenv("CONFIG_DIR", "/app/config")).resolve() DATA_DIR = Path(os.getenv("DATA_DIR", "/app/data")).resolve() # 出力先例 db_file = DATA_DIR / "db" / f"mmtrades_{mode}_{today}.db" log_path = DATA_DIR / "logs" / "MMBot.log"
🔧 docker-compose.yml
environment: CONFIG_DIR: ${CONFIG_DIR:-/app/config} DATA_DIR: ${DATA_DIR:-/app/data} volumes: - ${DATA_DIR}:${DATA_DIR}
成果:自由に切り替えられる開発環境
これにより、私の開発フローは次のようにシンプルかつ柔軟になった。
# ローカルホストのログ出力先を切り替え export DATA_DIR=$PWD/data_host docker compose -f compose/compose.yml up -d
コンテナ内から見れば /app/data
、
ホストから見れば ./data_host/
にログ・DB が記録されるようになった。
🧠 学び:固定パスは“使い捨て”になる
構造が固まっていない段階では、ハードコードされたパスは一見便利だ。
だが環境が増えた瞬間に切り替えが破綻し、構成の見直しコストが跳ね上がる。
パスを変数にし、構造を変えずに環境を切り替える仕組みを持つことこそが、長期的にメンテ可能なプロジェクトの第一歩だと強く実感した。
3. .env
管理とDocker Composeの整備
構造の可視化と CONFIG_DIR / DATA_DIR
の環境変数化を進める中で、次に直面したのが .env
ファイルの整理だった。
これまでは .env.testnet
や .env.production
がプロジェクトルート直下に雑多に置かれており、
「どれをどこで使っているのか?」 がわかりにくい状態だった。
また、docker-compose.yml
との接続もあいまいで、Compose実行時に正しい環境が読み込まれているかを確認する手段も乏しかった。
目標:役割別に.envを整理する
そこで次のように 役割ごとに .env
を分類する運用ルールを整備した。
ファイル | 目的 | 内容 |
---|---|---|
.env (プロジェクト直下) | Composeパーサ用 | DATA_DIR , MMBOT_RESTART など、ホストマシンの環境依存値のみ |
env/.env.testnet | Botに渡す環境変数(テストネット) | APIキー、SlackWebhook、MMBOT_MODE、戦略パラメータなど |
env/.env.production | Botに渡す環境変数(本番用) | 構成は testnet と同じだが、値が異なる |
この分離によって、どの変数がどこで効いているかが明確になり、管理・切り替えが非常に容易になった。
Docker Compose 側の変更点
# compose/compose.yml services: mmbot: env_file: - ../env/.env.testnet # ← 本番に切り替えるときは env/.env.production environment: CONFIG_DIR: ${CONFIG_DIR:-/app/config} DATA_DIR: ${DATA_DIR:-/app/data} volumes: - ${DATA_DIR}:${DATA_DIR} restart: "${MMBOT_RESTART:-unless-stopped}"
このように env_file:
では Bot内部に渡したい環境変数 を、environment:
や volumes:
では Compose実行時に展開したい環境変数 を使い分けることで、
柔軟かつ予測可能な構成が実現した。
.env.testnet
の中身(抜粋)
# MMBot テストネット用設定 MMBOT_MODE=testnet CONFIG_DIR=/app/config DATA_DIR=/app/data BYBIT_APIKEY=xxx BYBIT_SECRET=xxx SLACK_WEBHOOK_URL=https://hooks.slack.com/services/... SYMBOL=BTCUSDT S_ENTRY=5e-5 LOT=0.002 MAX_NOTIONAL_USD=1200 LOSS=10
.env
(プロジェクト直下)での最低限の定義
# Compose 展開専用 DATA_DIR=./data_host MMBOT_RESTART=unless-stopped
この .env
はコンテナには渡らないが、docker-compose.yml
内の ${VAR}
を展開する際に参照される。
運用時の実行例
# ローカル開発 export DATA_DIR=$PWD/data_host docker compose -f compose/compose.yml up -d
# testnet を起動 docker compose -f compose/compose.yml --env-file env/.env.testnet up -d
学び:envファイルは「読み手別」に分けると混乱が減る
これまで .env
を1枚に集約しようとして、ComposeパーサとBot内部の変数が混在していた。
だが、「誰がこの値を読むのか?」 という観点で切り分けることで、運用の明瞭さと安全性が一気に向上した。
特にDocker Composeは .env
の解決範囲が特殊(env_file:
とは別扱い)なため、それを前提に設計するだけでトラブルを予防できる。
4. entrypoint.sh
と cli.py
のつなぎ直し
構造を明確にし、環境変数を整備したことで、次に見直すべきは
**「コンテナ起動時のエントリーポイントから Python ロジックへの橋渡し部分」**だった。
従来のコードは最小限の構成ではあるが、
- JSON の生成場所や読み込み先がハードコードされていた
- 起動時のモード(testnet / mainnet)の判定が曖昧だった
DATA_DIR
に出力されるログやDBのパスが固定的で変更がきかない
といった問題を抱えていた。
これらを整理し直すことで、コンテナ起動フローの“見通し”と“安全性”が一気に改善した。
改修後の起動フロー構造
以下が、今回構築した起動処理の構造図である。
┌────────────────────┐ │ docker-compose.yml│ └────────┬───────────┘ │ ▼ ┌────────────────────┐ │ entrypoint.sh │ └────────┬───────────┘ (環境変数読み込み CONFIG_DIR, DATA_DIR, MMBOT_MODE) │ ▼ JSONテンプレートからconfigを生成(envsubst) │ ▼ JSON構文チェック(jq emptyでvalidate) │ ▼ Python CLIへ移行(exec python -m mmbot.cli) │ ▼ cli.pyがconfigを読み込み、Botのループ処理へ
entrypoint.sh
の改善ポイント
CONFIG_DIR=${CONFIG_DIR:-/app/config} DATA_DIR=${DATA_DIR:-/app/data} MODE=${MMBOT_MODE:-mainnet} CFG_TPL="${CONFIG_DIR}/mmbot/${MODE}/mmbot.tpl" CFG_JSON="/app/config_${MODE}.json" envsubst < "${CFG_TPL}" > "${CFG_JSON}" jq empty "${CFG_JSON}" exec python -m mmbot.cli "$@"
改善点:
CONFIG_DIR
/DATA_DIR
を外部から指定可能にMODE
が未指定でもデフォルトをmainnet
に固定jq
を使った構文チェックで テンプレート記述ミスを早期検出exec
によって Python プロセスを PID 1 に置換し、シグナル伝播を正しく処理
cli.py
側の受け口(抜粋)
CONFIG_DIR = Path(os.getenv("CONFIG_DIR", "/app/config")).resolve() DATA_DIR = Path(os.getenv("DATA_DIR", "/app/data")).resolve() cfg_root = Path(os.getenv("CONFIG_ROOT", "/app")) cfg_path = cfg_root / f"config_{mode}.json" db_dir = DATA_DIR / "db" db_dir.mkdir(parents=True, exist_ok=True) db_file = db_dir / f"mmtrades_{cfg['mode']}_{today}.db"
改善点:
- CONFIG_DIR/ROOT・DATA_DIR を受け取りつつ、Pathlib で安全に解決
- DB やログの生成先を DATA_DIR 配下の
db/
,logs/
に集約 "mmtrades_testnet_20240604.db"
など日付付きファイルで蓄積・分析しやすく
実際の動作ログ例(起動時)
[entrypoint] CONFIG_DIR=/app/config [entrypoint] DATA_DIR=/Users/xxx/MMBot/data_host ▶ generating /app/config_testnet.json from /app/config/mmbot/testnet/mmbot.tpl ▶ validating JSON syntax ▶ starting MMBot (testnet) 📌 NEW SESSION: mode=testnet, LOSS=10.0
このログが安定して出ることによって、「エラーのトレース」と「成功の再現」が圧倒的に楽になった。
学び:起点と終点を明示するとシステムは安定する
entrypoint.sh
の責務は「起動パラメータを整えて、CLIに橋渡しすること」。cli.py
の責務は「設定を読み取り、Bot本体のロジックを駆動すること」。
この責務分離がはっきりしたことで、どこを修正すれば何が変わるかが明確になり、安心して開発が進められた。
5. テストネット環境の正常起動確認
ここまでの構造整理とエントリーポイント修正を経て、いよいよ 本番に近い形でBotを実行するステップに進んだ。
対象は Bybit テストネット。
Slack通知やログ出力、ポジション建てと決済など、実際に「動く」Botとしての機能検証を一通り行った。
起動コマンド(testnet)
export DATA_DIR=$PWD/data_host docker compose -f compose/compose.yml up -d --build
DATA_DIR
はホストの./data_host
に設定compose.yml
はenv/.env.testnet
を読み込む- 起動後、ログ確認:
docker compose -f compose/compose.yml logs -f --tail 50 mmbot
起動ログに現れた「成功」の兆候
[entrypoint] CONFIG_DIR=/app/config [entrypoint] DATA_DIR=/Users/xxx/MMBot/data_host ▶ generating /app/config_testnet.json from /app/config/mmbot/testnet/mmbot.tpl ▶ validating JSON syntax ▶ starting MMBot (testnet) 📌 NEW SESSION: mode=testnet, LOSS=10.0
このログが示すのは:
- テンプレート読み込みと変数展開が成功
- 生成された JSON に構文エラーなし
- main loop に入る前の初期化が完了
- モードは
testnet
として正しく起動
Slack通知も正常
- 起動時に "📌 NEW SESSION" のSlack通知を受信
- 以降のサイクルごとに、スプレッド条件・注文結果・PnLが報告される
通知例(抜粋):
📈 *MMBot* cycle 3 spread `0.00045` Buy @xxx → Fill Sell@xxx → Fill *Cycle PnL* `+3.22` *Total PnL* `+6.78`
これにより、Botの健全な稼働とパフォーマンスをリアルタイムで可視化できている。
実際にBybit テストネットで注文も確認
テストネットの UI 上でも、Botが出した注文が反映され、約定が発生していた。
これにより、CLI → WebSocket → REST API 呼び出し → 注文処理の全パスが通ったことを意味する。
ホスト側の出力も問題なし
ls -R data_host/
出力例:
data_host/ ├── db │ └── mmtrades_testnet_20240604.db ├── logs │ └── MMBot.log
- SQLite DB は日付単位でファイル出力され、
run_loop()
のPnLロジックと連動 - ログファイルも
loguru
を通じてlogs/
に書き込まれており、追跡が可能
学び:構造の整理は「運用の可視化」に直結する
このフェーズで最も感じたのは、構造が整理されていれば「本当に動いているか?」の確認も簡単になるという点だ。
- 通知が来る
- ログが伸びる
- DBができる
- UIで注文が見える
という 「多方面からの確認」がすべて可視化された状態は、
今後の本番稼働・異常検知・改善ループを支える非常に強固な基盤になる。
6. 成功の鍵は「構造の可視化」だった
今回の環境構築とリファクタは、わずか半日でtestnet稼働まで到達するという、想定以上のスピードで進行した。
その背景には間違いなく、「構造を自分の手で可視化していたこと」がある。
構造の“見える化”がもたらしたもの
私が最初に行ったのは、プロジェクト全体の構成を手書きで整理することだった。
これは単に「どこに何があるか」を書き出すだけではなく、
- どのスクリプトがどのファイルに依存しているか
- 起動フローがどう進み、どこで何が生成・参照されるか
- 外部との接続点(Slack、Bybit API、ログ/DB出力)がどこにあるか
といった“動線と接点のマッピング”を行う作業だった。
結果:コード提案の背景が“読める”ようになった
この構造を把握していたことで、AIから提案された変更にも 「何を、どこで、なぜ変えようとしているか」 が直感的に理解できた。
たとえば、
environment: CONFIG_DIR: ${CONFIG_DIR:-/app/config}
といった提案を受けたときも、
「それを変更すると entrypoint.sh
の JSON 出力先が変わり、それを cli.py
が読む構造になっているから、env_file
で変数を渡す必要があるな」とすぐに頭の中でつながった。
「つながりの見える」開発は、迷いを減らす
このような構造を把握した開発では、次のような変化が起こる:
Before | After |
---|---|
「これを書き換えていいのかわからない」 | 「これは〇〇に影響するから書き換える必要がある」 |
「提案されたコードを試すのが怖い」 | 「その変更が何を起こすかが事前にイメージできる」 |
「バグが起きたとき、原因を探すのに時間がかかる」 | 「構造的に見ると、おそらくこのあたりが怪しい」と当たりがつけられる |
構造 × ロジックの“二層思考”へ
この体験を通じて、私の中に新たな視点が形成された。
コードはロジックだけでなく、構造の中で意味を持つ。
だからこそ、
- 「動くコード」を作るだけでなく
- 「運用に耐える構造」を作ることが、長期的には効率と自信をもたらす
という開発観が強くなった。
7. まとめ:今日の達成と今後の一手
今回の開発では、プロジェクトの構造整理からtestnet稼働までを半日で完了させることができた。
これは単なる作業の積み上げではなく、「構造を見通す視点」を持って取り組んだ結果だと感じている。
✅ 今日の達成まとめ
項目 | 内容 |
---|---|
構造の見直し | パス・スクリプト・設定ファイルの依存関係を整理し、構造図を描いた |
CONFIG_DIR / DATA_DIR の環境変数化 | ハードコードを撤廃し、運用環境ごとの柔軟な切り替えが可能に |
.env の分離設計 | Compose向けとBot内部向けで役割を分けたenvファイル管理を実現 |
entrypoint.sh ⇄ cli.py の接続整理 | JSON生成、構文検証、PID引き継ぎ、ログ出力のルートを明確化 |
テストネットでの安定稼働 | Slack通知、実注文、ログ出力、PnL蓄積がすべて正常に動作することを確認 |
🧠 獲得した学び
- 構造を可視化すると、迷わずに速く動ける
- “どのスクリプトが何を担っているか”がわかっていれば、提案や修正も自信を持って進められる
- AIの提案は、こちらの構造把握力に比例して理解・活用しやすくなる
🔜 次に進むべき一手
タスク | 内容 |
---|---|
✅ watchdog.sh の cron登録 | 擬似障害通知 → Slack → restart をテストネットで試す |
✅ CIへの dry-run 組み込み | python -m mmbot.cli --dry-run を含むビルドテストで本番安全性担保 |
✅ .env.production の整備 | 本番APIキーとパラメータを反映。実環境で稼働準備へ |
✅ PRの作成とCI通過 | feat/config-data-env ブランチをGitHubにpushし、コードレビュー&マージへ |
⏳ mainnet初回稼働テスト | 小ロットでの本番動作検証を開始予定 |
✍️ 最後にひとこと
構造を見直すことは、時間がかかるようでいて実は一番の近道だった。
その構造さえ見えていれば、複雑なスクリプトでも恐れることなく変更できるし、
外部と連携していく土台にもなる。
この日をきっかけに、私は単にBotを動かす開発者から、
構造をコントロールする運用者へ一歩踏み出せたような気がする。