Bot mmnot 開発ログ

🛠️開発記録#215 (2025/5/9) MMBot 開発ログ 20「Mainnet ミニマム運用 - env 分離と“静かな”24h 監視体制」

2025年5月9日

前回の記事に引き続き、今回も仮想通貨botの開発状況をまとめていきます。


対象読者

  • Bybit で “まずは 0.1 USDT/回の実弾” を試したい個人開発者
  • .envMakefile で「公開しない設定ファイル」を安全に回す方法を知りたい人
  • ログを tail しながら“何も起こらない”を確認する運用フローを学びたい人

1. この記事で得られるもの

トピック一行まとめ
env 分離の最終形.env.prod / .env.test + config_mainnet.tpl で誤発注ゼロへ
Makefile 自動生成make mainnet-loopconfig_mainnet.json を envsubst → Docker 起動までワンショット
Mainnet ミニマム回しlot=0.0001 BTC, DD=10 USD, 無限ループで 24 h 放置
WARN だけリアルタイム監視make logs-warn を別タブで tail -F
“Fill しない” の原因調査スプレッド/閾値/手数料を CLI 1 行で可視化&納得感

2. 本日の作業ログ

2-1. シークレットを envtpljson へ流す一本線

# 1) .env.prod に MAINNET キーだけ保管
BYBIT_KEY_MAIN=xxxxxxxx
BYBIT_SECRET_MAIN=xxxxxxxx
MAX_NOTIONAL_USD=50
LOT=0.0001
# ほか Slack URL 等…

# 2) テンプレ (config_mainnet.tpl) には ${VAR} を書く
{
  "bybit": { "apiKey": "${BYBIT_KEY_MAIN}", "secret": "${BYBIT_SECRET_MAIN}" },
  "bot":   { "max_notional": ${MAX_NOTIONAL_USD}, "lot": ${LOT}, ... }
}

# 3) Makefile で自動生成 ➜ Docker コンテナへ渡す
make mainnet-loop ENV=.env.prod

ポイント

  • 生成した config_mainnet.json には秘密キーが平文で入る。必ず .gitignore
  • rm -f config_mainnet.json 後に再生成 → “古いキーが残る事故” を防止。

2-2. Mainnet 最小ロットで 24 h 監視フロー

ターミナルコマンド役割
Amake mainnet-loop S_ENTRY=1e-6Bot 本体(Docker)
Bmake logs-warnlogs/warn.log を tail -F
C`watch -n 60 'curl …orderbook…jq'`
  • lot: 0.0001 BTC
  • DD ガード: 10 USD
  • max_notional: 50 USD
  • 結果: 現在まで Fill 0、DD 0 — “静かな成功”

Why Quiet?

  • ask-bid スプレッドが 0.1 bps しか開かず、S_ENTRY=1bps (1e-6) を超えなかった。
  • Bot が“何もしない”=想定どおり。間違って高値掴みするより 100 倍安全。

3. つまずき & 解決メモ

つまずき原因解決
envsubst 後に ${…} が残る.env.prod に変数が未定義.env.prod に BYBIT_KEY_MAIN 等を追加
JSONDecodeErrorconfig_mainnet.tpl 内の数値を Quote していた${MAX_NOTIONAL_USD} など 数値は裸に修正
TypeError: '>' not supported between float & strMAX_NOTIONAL_USD が文字列float(env or cfg) へキャスト、テストコード追加
Fill が来ないスプレッド激狭curl+jq 一行コマンドで即計測、S_ENTRY を再考

4. 今日の学び 🎓

学び初学者への刺さるポイント
“何もしない”もテストMainnet 1 bps 条件で Fill 0 = 手数料負けしない安全証明
tpl → json パイプラインenvsubst 前後で jq を挟めばバグは 90 % 消える
WARN だけ tail成功ログは黙らせ、異常だけ即 Slack & 端末に集約
Spread 可視化ワンライナー```bash
curl -s 'https://api.bybit.com/v5/market/orderbook?category=linear&symbol=BTCUSDT' | jq -r '.result | (.a[0][0]

5. 次の To-Do(優先順)

  1. 自動スプレッドログ
    • orderbook.1 の ask-bid を 10 秒ごとに DB へ → 可視化準備
  2. pytest + gitleaks CI
    • env キャスト & JSON 生成テストを GitHub Actions に固定
  3. DD ガード閾値の昼夜切替
    • 00:00 UTC で daily_loss_cap をリセット
  4. tools/agg.py で 24 h PnL グラフ
    • 1 行 python tools/agg.py → Markdown 出力
  5. パラメータ sweep
    • Actions matrix で S_ENTRY & LOT を自動検証(Testnet)

6. botter スコア (by GPTo3)

項目配点評価
問題切り分け力3026 – 401 / TypeError を即特定
自動化・再現性2015 – Makefile ◎、CI は雛形のみ
リスク管理視点2017 – DD guard & MAX_NOTIONAL を実戦投入
ドキュメント整備1510 – README とコードコメントを随時更新
開発ペース & 共有1514 – Slack / Chat で高頻度共有

総合 = 82 / 100

80+ =「実弾長期運用ライン」突破
CI 緑化と PnL 可視化が完了すれば 90 点台が見えてきます。


7. まとめ

「静かな成功」を検証する日
今日は Fill 0 でも OK。‐ リスクを最小に絞った Mainnet 運用が 問題なく 6 h 以上走る ことを確認できました。今後はスプレッド/ボリュームに応じてダイナミックに S_ENTRY を調節するロジックを足しつつ、CI と可視化を整えて “寝ていても見える” ボットへ育てていきます。


この記事は学習目的の技術ログです。
コード断片は MIT License で公開、利用は自己責任で!

👇ラジオで話したこと


きょうは MMBot を 最小ロットでメインネット24 時間監視 へ出すまでの手順を、初心者にも伝わるように解説します。鍵は envファイルを分離 して誤発注を防ぎ、Makefile でワンコマンド起動、DDガード+Slack で含み損が膨らむ前に自動ブレーキを踏む仕組み。最後に「何も起こらない=静かな成功」をログで確認するコツまで一気にいきましょう。

用語解説

1. SQLite(エスキューライト)

  • 何者?
    スマホやブラウザにも組み込まれている“ファイル1個で完結する”軽量データベース。サーバーを立てずに mydata.db だけで SQL が使えるのが最大の強みです。sqlite.orgMedium
  • ボット開発での役目
    取引履歴やログをとりあえず保存する“メモ帳”代わり。外部クラウド DB を契約する前のプロトタイプ段階でも十分実用レベル。

2. VACUUM(バキューム)

  • 読み方と意味
    「バキューム」。SQLite 専用コマンドで、使わなくなった領域を掃除機のように吸い取り、DB ファイルを最小サイズに再パックします。sqlite.orgTutorialsPointSQLite Tutorial
  • いつ走らせる?
    ▷ ログが大量に削除された後/長期運用でファイルが肥大化してきた時。
    ▷ 本番トラフィックが少ない深夜帯に cron で実行するのが一般的です(実行中はロックが掛かるため)。

3. ${VAR}(ドル・ブレイス・バー)

  • 正体はシェルの「環境変数展開」記法
    Bash などで ${API_KEY} と書くと、実行時に変数 API_KEY の値へ置き換わります。GNUStack Overflow
  • Bot 構成での使い方
    1. .env ファイルに 秘密キーを保存
    2. envsubst コマンドで JSON テンプレート中の ${VAR} を本物の値に差し替え
      ⇒ ソースや Git にはキーが一切残らない安全フローが完成。

4. S_ENTRY(エス・エントリー)

  • 何の略?
    Spread Entry Threshold(スプレッド開始閾値)の略称として、開発中の MMbot で使っているパラメータ名。
  • 役割
    市場のビッドーアスク差S_ENTRY 以上に広がったときだけ指値を出して利ザヤを狙う安全装置。Mediumsolutionshub.epam.com
    値を小さくすると約定しやすいが手数料負けしやすく、大きくすると“静かな”待機時間が増えます。運用中に spread を計測して動的に調整するアイデアも検討中。

まとめ一言

SQLite で履歴を貯め、必要なら VACUUM でお掃除
.env × ${VAR}秘密を守りつつ、S_ENTRY を調整しながらマーケットに指値を置く。


1️⃣ “env 分離” の最終形

.env.prod とテンプレート

  • シークレットは .env にだけ書く。たとえば
    BYBIT_KEY_MAIN=xxxxxxxx
    こうして Git には置かない。 .gitignore に必ず追加します 🛑Stack Overflow
  • テンプレート config_mainnet.tpl 側は以下のような “穴あき” 形式にしておくのがコツ。
{ "apiKey": "${BYBIT_KEY_MAIN}", "lot": ${LOT} }

envsubst で穴埋め

  • GNU の envsubst コマンドが ${VAR} を環境変数で置換してくれますUnix & Linux Stack Exchange
  • Makefile から 以下のように1 行呼べば tpl → json を自動生成。
envsubst < $< > $@

?= で “デフォルト値”

  • Makefile の ENV ?= .env.prod「すでに値が来ていれば上書きしない」 条件付き代入です。GNU
    make mainnet-loopmake run ENV=.env.test … を同じレシピで切替可能。

2️⃣ Makefile ワンショット起動

run: $(CONFIG)
	set -a && source $(ENV) && set +a && \
	docker compose run --rm --entrypoint "" $(SERVICE) \
	  python -m MMbotbybit.MMbotbybit \
	  --lot $(LOT) --max_loss_usd $(LOSS) ...
  • set -a.env を読んだら全部 export。Python が os.getenv() で受け取れます。
  • LOT=0.0001 LOSS=10 のように コマンド行は後勝ち =テストが捗る。

3️⃣ DD(ドローダウン)ガード+Slack 通知

● 含み損も監視する理由

Bybit では未決済 PnL が一定を超えると追加証拠金、最悪ロスカットになりますバイビット
だから 確定損益+未確定損益 の合計でラインを切ります。

unreal = await get_unreal_pnl(...)
if realised + unreal <= -max_loss:
    await close_all_positions(...)
    await notify_slack("🔥 DD guard hit…")
  • 未確定損益は v5 /v5/position/list で取得可能バイビット
  • Slack への送信は Incoming Webhook を 1 URL 発行すれば OKSlack API

4️⃣ “静かな成功” をどう見る?

● 最小ロット運用

Bybit の BTCUSDT 最小数量は 0.001 BTC と公式にありますバイビット
今回は lot=0.0001 BTC でまず注文が立たないことを確認するテスト。

● tail -F で WARN だけ追う

  • make logs-warntail -F logs/warn.log
  • -F は「もしファイルがローテートされても自動で追跡し続ける」オプションですUnix & Linux Stack Exchange
    → コンテナ再起動や日次ローテーションでも監視が途切れません。

● Spread 可視化ワンライナー

watch -n 60 \
  "curl -s 'https://api.bybit.com/v5/market/orderbook?symbol=BTCUSDT&category=linear' | \
   jq -r '.result | (.a[0][0] - .b[0][0])'"

Bybit の orderbook API で ask-bid を直接チェックバイビット
スプレッドが設定値 0.1 bps 未満なら Bot は「何もしない」=狙い通りの安全モードです。


5️⃣ ログと DB のお掃除

  • VACUUM — SQLite ファイルを再パックし、ディスクを節約しますSQLite
    Makefile の loguru rotation と合わせて “ディスク枯渇” を予防。

6️⃣ “見落としがちチェック” TOP3

✅ チェックなぜ要る?具体アクション
Testnet/Mainnet 完全分離誤発注ゼロENV ?= .env.test, 切替コマンドだけ .env.prod
Slack ノイズ制御夜中に爆音通知しない.envSLACK_LEVEL=ERROR
DB & ログ回転SSD いっぱい事件回避rotation="00:00" と日次 VACUUM

7️⃣ きょうの学び

  1. env → tpl → json の一本線を作ると「どこに秘密を書くか」が迷子にならない。
  2. DDガードは“含み損も足す” これで「ロスカット寝落ちリスク」を激減。
  3. “静かな成功”を測る—Fill 0 でもロジックは通っていると分かる観測点を置く。
  4. 小さなコミットを頻繁にgit revert で即ロールバックできる保険になる。GitHub

「きょうは“何も起きない”を証明する回でした。次は動かすための微調整、いきましょう!」


深掘りしたいテーマ

  • VACUUM の実行タイミング、夜中00:00?それとも低負荷帯?
  • Slack 通知をさらに減らす “サイレンス時間帯” 実装
  • Spread に応じて自動で S_ENTRY を変えるアルゴのアイデア

おまけトーク①

「VACUUM は 何時に回すのがベスト?」

SQLite の VACUUM、みなさん何時に走らせてますか?
結論から言うと──“夜中 0 時 ではなく、Bot が自然にヒマになるタイミング” がベターです。

● 00 時ジャストは意外と混む

  • Cron 既定で 0 時にログローテートやバックアップを走らせる文化が多い。
  • VPN 経由のラップトップなど、CPU が一瞬スパイクして約定遅延のリスク。

● 負荷メータを基準にする例

while :; do
  load=$(awk '{print $1*100}' /proc/loadavg)
  if [ "$load" -lt 50 ]; then
      sqlite3 trades.db 'VACUUM;'
      echo "🧹 VACUUM done"; break
  fi
  sleep 600   # 10 分おきに再チェック
done

「CPU 使用率 50 %以下」など数字で閾値を切ると、“実際に空いた瞬間” にだけ掃除してくれます。

● テストネットなら日次、本番は週次で十分

  • 本番環境は Logrotate 圧縮も合わせて週イチで OK。
  • 重要なのは「VACUUM 中は WAL を OFF」して書込み衝突を避けるか、WAL モードのまま PRAGMA wal_checkpoint; で先にフラッシュするか、運用方針を決めること。

要するに『0 時だから掃除』ではなく、『Bot が寝てるタイミングで掃除機を当てる』。これが SQLite と同居する Bot の黄金律です。


おまけトーク②

「Slack を静かにさせる “サイレンス時間帯” 実装」

深夜 1 時にスマホが鳴り続けて寝不足──そんなあなたに“サイレンス時間帯”を導入するお話です。

from datetime import datetime, time
SILENT_START = time(1, 0)   # 01:00
SILENT_END   = time(6, 0)   # 06:00

def should_notify(level: str) -> bool:
    now = datetime.now().time()
    in_silent = SILENT_START <= now <= SILENT_END
    if in_silent and level in ("INFO", "WARNING"):
        return False
    return True
  1. 時刻フィルタ –– INFO / WARN は深夜帯だけ握りつぶす。
  2. ERROR 以上は強制送信 –– 本当に落ちたときだけバイブが鳴る。
  3. レベルを環境変数へ –– .env: SLACK_LEVEL=ERROR で昼夜同じコードが動く。

ハマりどころ

  • Docker の TZ が UTC のままだと 9 時間ズレる → TZ=Asia/Tokyo を通す。
  • Slack スレッドごとに通知オフでも OK。ただし Bot がチャンネル権限を失うとエラーが出るので要チェック。

「“鳴らさない勇気”を Bot に教える──これが長期運用のメンタルヘルスです。」


おまけトーク③

「スプレッド連動で S_ENTRY を自動調整するアイデア」

最後はアルゴリズムの種明かし。固定 S_ENTRY じゃ板が動いた瞬間に置いてけぼり──そこでスプレッド連動アルゴです。

① “板厚スケーリング” 方式

  1. Orderbook から トップ ask–bid を 1 秒ごと取得。
  2. 実スプレッド × α(たとえば 1.2 倍)を当日の S_ENTRY にする。
    • スプレッドが 0.05 bps なら S_ENTRY = 0.06 bps
    • 板が開けば自然にエントリー幅も広がる=Fill チャンスアップ。

② “分位点” 方式

  • 過去 N 分のスプレッド分布をリングバッファに保存。
  • 70 パーセンタイル値をリアルタイムで再計算 ➜ “ほぼ埋まるが滑らない”幅をキープ。
hist.append(spread)
s_entry = np.percentile(hist, 70)

③ “手数料境界” 方式(安全寄り)

  • taker 手数料 0.06 %、maker -0.02 % → 差額 0.08 %が赤字ライン
  • S_ENTRY を手数料+安全マージン 0.01 bps 上に置く。
  • 手数料改定に合わせて自動で更新されるのもメリット。

実装ポイント

チェックWhyHint
API 呼びすぎRateLimit 超過WebSocket orderbook を 1 秒サンプリング
過剰追従スプレッドのノイズを拾う指数平滑 α=0.2 くらいで滑らかに
サイズ不足0.0001 BTC が置けないminQty API を先にキャッシュ

要は“板の呼吸に合わせてエントリー位置を動かす”。
スプレッドが広いときはガッツリ抜いて、狭いときは無理せずパス――これだけで PnL がブレにくくなります。


「VACUUM のタイミング、通知を黙らせる工夫、そして動的 S_ENTRY──全部“安全側に倒す”ための知恵でした。今後の放送では、このアルゴを実戦投入した結果をレポートします。どうぞお楽しみに!

最後まで聴いてくださってありがとうございます。また次回の放送でお会いしましょう。よだかでした!

-Bot, mmnot, 開発ログ