前回の記事に引き続き、今回も仮想通貨botの開発状況をまとめていきます。
対象読者
- Bybit で “まずは 0.1 USDT/回の実弾” を試したい個人開発者
.env
とMakefile
で「公開しない設定ファイル」を安全に回す方法を知りたい人- ログを tail しながら“何も起こらない”を確認する運用フローを学びたい人
1. この記事で得られるもの
トピック | 一行まとめ |
---|---|
env 分離の最終形 | .env.prod / .env.test + config_mainnet.tpl で誤発注ゼロへ |
Makefile 自動生成 | make mainnet-loop で config_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. シークレットを env → tpl → json へ流す一本線
# 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 監視フロー
ターミナル | コマンド | 役割 |
---|---|---|
A | make mainnet-loop S_ENTRY=1e-6 | Bot 本体(Docker) |
B | make logs-warn | logs/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 等を追加 |
JSONDecodeError | config_mainnet.tpl 内の数値を Quote していた | ${MAX_NOTIONAL_USD} など 数値は裸に修正 |
TypeError: '>' not supported between float & str | MAX_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(優先順)
- 自動スプレッドログ
orderbook.1
の ask-bid を 10 秒ごとに DB へ → 可視化準備
- pytest + gitleaks CI
- env キャスト & JSON 生成テストを GitHub Actions に固定
- DD ガード閾値の昼夜切替
- 00:00 UTC で
daily_loss_cap
をリセット
- 00:00 UTC で
- tools/agg.py で 24 h PnL グラフ
- 1 行
python tools/agg.py
→ Markdown 出力
- 1 行
- パラメータ sweep
- Actions matrix で
S_ENTRY
&LOT
を自動検証(Testnet)
- Actions matrix で
6. botter スコア (by GPTo3)
項目 | 配点 | 評価 |
---|---|---|
問題切り分け力 | 30 | 26 – 401 / TypeError を即特定 |
自動化・再現性 | 20 | 15 – Makefile ◎、CI は雛形のみ |
リスク管理視点 | 20 | 17 – DD guard & MAX_NOTIONAL を実戦投入 |
ドキュメント整備 | 15 | 10 – README とコードコメントを随時更新 |
開発ペース & 共有 | 15 | 14 – 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 構成での使い方
.env
ファイルに 秘密キーを保存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-loop
とmake 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…")
4️⃣ “静かな成功” をどう見る?
● 最小ロット運用
Bybit の BTCUSDT 最小数量は 0.001 BTC と公式にありますバイビット。
今回は lot=0.0001 BTC でまず注文が立たないことを確認するテスト。
● tail -F で WARN だけ追う
make logs-warn
はtail -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 ノイズ制御 | 夜中に爆音通知しない | .env に SLACK_LEVEL=ERROR |
DB & ログ回転 | SSD いっぱい事件回避 | rotation="00:00" と日次 VACUUM |
7️⃣ きょうの学び
- env → tpl → json の一本線を作ると「どこに秘密を書くか」が迷子にならない。
- DDガードは“含み損も足す” これで「ロスカット寝落ちリスク」を激減。
- “静かな成功”を測る—Fill 0 でもロジックは通っていると分かる観測点を置く。
- 小さなコミットを頻繁に。
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
- 時刻フィルタ –– INFO / WARN は深夜帯だけ握りつぶす。
- ERROR 以上は強制送信 –– 本当に落ちたときだけバイブが鳴る。
- レベルを環境変数へ ––
.env
:SLACK_LEVEL=ERROR
で昼夜同じコードが動く。
ハマりどころ
- Docker の TZ が UTC のままだと 9 時間ズレる →
TZ=Asia/Tokyo
を通す。 - Slack スレッドごとに通知オフでも OK。ただし Bot がチャンネル権限を失うとエラーが出るので要チェック。
「“鳴らさない勇気”を Bot に教える──これが長期運用のメンタルヘルスです。」
おまけトーク③
「スプレッド連動で S_ENTRY を自動調整するアイデア」
最後はアルゴリズムの種明かし。固定 S_ENTRY
じゃ板が動いた瞬間に置いてけぼり──そこでスプレッド連動アルゴです。
① “板厚スケーリング” 方式
- Orderbook から トップ ask–bid を 1 秒ごと取得。
- 実スプレッド × α(たとえば 1.2 倍)を当日の
S_ENTRY
にする。- スプレッドが 0.05 bps なら
S_ENTRY = 0.06 bps
。 - 板が開けば自然にエントリー幅も広がる=Fill チャンスアップ。
- スプレッドが 0.05 bps なら
② “分位点” 方式
- 過去 N 分のスプレッド分布をリングバッファに保存。
- 70 パーセンタイル値をリアルタイムで再計算 ➜ “ほぼ埋まるが滑らない”幅をキープ。
hist.append(spread) s_entry = np.percentile(hist, 70)
③ “手数料境界” 方式(安全寄り)
- taker 手数料 0.06 %、maker -0.02 % → 差額 0.08 %が赤字ライン。
S_ENTRY
を手数料+安全マージン 0.01 bps 上に置く。- 手数料改定に合わせて自動で更新されるのもメリット。
実装ポイント
チェック | Why | Hint |
---|---|---|
API 呼びすぎ | RateLimit 超過 | WebSocket orderbook を 1 秒サンプリング |
過剰追従 | スプレッドのノイズを拾う | 指数平滑 α=0.2 くらいで滑らかに |
サイズ不足 | 0.0001 BTC が置けない | minQty API を先にキャッシュ |
要は“板の呼吸に合わせてエントリー位置を動かす”。
スプレッドが広いときはガッツリ抜いて、狭いときは無理せずパス――これだけで PnL がブレにくくなります。
「VACUUM のタイミング、通知を黙らせる工夫、そして動的 S_ENTRY──全部“安全側に倒す”ための知恵でした。今後の放送では、このアルゴを実戦投入した結果をレポートします。どうぞお楽しみに!
最後まで聴いてくださってありがとうございます。また次回の放送でお会いしましょう。よだかでした!