【思考ログ】
・最適なエントリとは、fill 確率と exit 確率を掛け合わせた期待値が最大になる瞬間に注文を出すことであり、
・それに対して、Exitとは、「市場に向けて抱いた仮説(≒entry)を、現実として精算するフェーズ」である。— よだか(夜鷹/yodaka) (@yodakablog) June 7, 2025
1. はじめに ─ FR × Market Making が生む “二重のエッジ”
「同じ 1 ドルを 2 回稼ぐ」――
Funding Rate(FR)とマーケットメイク(MM)を組み合わせると、まさにそんな構造的メリットが得られます。ここでは 「なぜ FRMMbot なのか」 を数字ベースで具体的に分解し、以後の開発フロー(生存 → エントリ → エグジット)全体の狙いを腹落ちさせます。
1-1. そもそも Funding Rate とは何をくれるのか?
| 項目 | 概要 | 超ざっくり式 |
|---|---|---|
| 支払い / 受取りサイクル | 多くの取引所で 8 時間ごと | PnL_FR = Position_Notional × (FR% / 8h) |
| 正の FR | ロングがショートに支払い → ショート側が現金収入 | 例)FR = +0.04 %/8h → $100 k ショート = +$40 |
| 負の FR | ショートがロングに支払い → ロング側が現金収入 | 逆パターン |
ポイント
- 建玉量 × 保有時間 がそのまま “利息収入” に直結
- 値動きによるキャピタルゲイン / ロスとは 完全に別勘定 のインカム
1-1-a. 「FR が掛かる/掛からない」商品の違い
| 商品カテゴリ | 例 | FR の有無 |
|---|---|---|
| 永久先物 (Perpetual, Perp) | BTC-USDT Perp | ○(8 hごと) |
| 現物 / 線形先物 | BTC (Spot)、BTC-USDT Quarterly | ✕(または極小) |
要点: FR は Perp 側の純残高 にだけ発生。
現物や線形先物でヘッジを取れば、そのヘッジ分には FR が掛からない。
1-2. マーケットメイクが生む “スプレッド刈り取り” エッジ
- 発注ペア: Bid(買い)と Ask(売り)を同時に板に置く
- 目標: Bid 約定 → Ask も約定(または逆順)で在庫 0
- 利益公式:
PnL_MM = Ask_Fill_Price – Bid_Fill_Price – 手数料 – スリッページ
1-3. “二重のエッジ” が同時に走ると何が起きる?
| 収益項目 | タイミング | 具体例 (マイナス FR = –0.03 %/8h) |
|---|---|---|
| PnL_MM (スプレッド利益) | 約定直後 〜 数分 | 板スプレッド分をロックイン |
| PnL_FR (利息収入) | 建玉保有中に 8 h ごと | Perp Long 1 BTC → 受取 $30/8h |
| ±PnL_ΔPrice (価格変動) | 随時 | 現物 or 線形先物ショートでほぼ相殺 |
具体ポジション図(正解パターン)
商品 建て方向 FR 目的 Perp Long 1 BTC 受取 マイナス FR インカム 現物 / 線形先物 Short 1 BTC 0 ΔPrice ヘッジ
⚠️ やってはいけない例
Perp を ショートして現物ロングでヘッジすると、Perp 側で FR を支払うだけ になり逆ザヤ。
Delta neutral ≠ FR neutral に注意。
用語メモ
- ΔPrice … ロングとショートの価格差による損益。
- Δ(デルタ)ニュートラル … ロングとショートの数量を釣り合わせて価格変動リスクをゼロ近辺に抑えた状態(略して デルニュー)。
Key Insight
- 板スプレッドで “即金” を確定し、
- Perp ロングで FR “利息” を 8 h ごとに受け取る。
この 時間軸が異なる 2 本立て収益 が FRMMbot の核心。
片方が不調でももう片方が救済できるため、損益曲線が安定しやすい。
1-4. なぜ「リスク最小フェーズ」から開発を始めるのか?
| 開発フェーズ | 失敗した場合の被害 | 優先度 |
|---|---|---|
| ① 生存性ガード (リスクリミッタ / safe_exit 骨格) | 破産 | ★★★★★ |
| ② エントリ最適化 | 利益チャンスを逃す | ★★★★☆ |
| ③ エグジット最適化 | 損失が拡大する | ★★★★☆ |
FR 収入を得るには 最低 8 h 保有 が前提 ⇒ ポジション滞留が伸びる。
だからこそ 「即死しない地盤」 を先に固めなければ、改良サイクル前に資金が溶けてしまう。
1-5. この記事で扱うスコープ
| セクション | コンテンツ |
|---|---|
| 2 | 生存性ガードの具体ロジック(証拠金管理・強制 safe_exit) |
| 3 | エントリ最適化:スプレッド検知・fill 確率モデル |
| 4 | エグジット最適化:利確最大化 & 損切り最小化アルゴリズム |
| 5 | 勝ち/負けの定義と言語化、ログ設計 |
| 6~ | 開発サイクル/戦略重心の決め方/実装テンプレート集(Mermaid, code snippets) |
これで “なぜ FRMMbot を作るのか、どこに収益源があるのか” を押さえた上で、
次章から具体的な設計フェーズに入ります。
2. 生存性ガード ─ 「死なない Bot」を先に作る
「儲ける前に、まず溶けない」—
FR × MM Bot は 建玉が長く残る 可能性があるため、
レバレッジ 1 × でも 資金拘束と急変ドローダウン の両面リスクを抱えます。
ここでは “最低限の命綱” をロジックで縫い付ける方法 を、実装レベルの粒度でまとめます。
2-1. どこで死ぬのか? ― 主要クラッシュパターン
| # | パターン | 原因 | 発生速度 | 想定ダメージ |
|---|---|---|---|---|
| A | 片側 fill ➜ 逆行急伸 | 板スカスカ or 片通し | 数秒 | 0.5 – 3 % |
| B | FR 逆転 | 直前でポジ側入替 | 8 h ブロック | 0.1 – 0.3 % / 8 h |
| C | Exit 注文通らず滞留 | 板厚急増・回線遅延 | 10 – 60 s | 未確定だが無限膨張 |
| D | システム落ち | コンテナ停止・WS 落ち | 任意 | その瞬間以降すべて |
2-2. 証拠金管理 — “何%動いたら撤退” をハードコード
# settings.py
MAX_LOSS_PCT = 0.6 # -0.6% で即撤退
MAX_NOTIONAL_USD = 50_000 # ポジ総量キャップ
RISK_UNIT_USD = 2_500 # 1 ロットあたりの最大ノッチ
# entry 時のガード
if current_notional + order_size_usd > MAX_NOTIONAL_USD:
skip("notional cap") # 入口でブロック
MAX_LOSS_PCTは必ず絶対値で。所要証拠金 × 0.006 が想定ロス上限。MAX_NOTIONAL_USDを超えるサイズは エントリ拒否 で回避。- 取引所のリミットオーダー上限が別にある場合も、まず自前で叩く。
2-3. safe_exit() ステートマシン
stateDiagram-v2
[*] --> Idle
Idle --> Check: ポジ残有無?
Check --> PanicExit: PnL < -MAX_LOSS_PCT
Check --> TimedExit: PosAge > MAX_HOLD_SEC
PanicExit --> Verify
TimedExit --> Verify
Verify --> Idle: 完全フラット
| ステート | 主処理 | 例外時のフォールバック |
|---|---|---|
| PanicExit | 成行 で残ポジ全閉じ | API エラー → 100 ms 再試行 × N |
| TimedExit | 板厚見ながら 分割 exit | 30 s 越え → 強制 Market |
| Verify | 残量 0.000 BTC? | Yes→Idle / No→PanicExit |
ワンポイント
- Verify 失敗 → 即
PanicExit再入 で “永久ループ脱出” を保証。- ログは
exit_reason,attempt_id,elapsed_ms,fill_pctを必ず残す。
2-4. ウォッチドッグ sidecar — Bot 死亡検知&Slack 通知
# infra/watchdog/watchdog.sh (抜粋)
check_loop() {
DEAD=$(docker inspect -f '{{.State.Status}}' mmbot)
if [ "$DEAD" != "running" ]; then
curl -s -X POST -d "payload={\"text\":\"🛑 MMBot stopped: $DEAD\"}" $SLACK_WEBHOOK_WATCHDOG
docker stop mmbot # position freeze
fi
}
- 300 s 間隔でコンテナをポーリング。
- 停止検知 → Slack → sidecar 自身で mmbot を
stop(exit 処理のみ動かす構想)。 - この仕組みだけで PC 再起動 / Docker 再起動 の大半を安全側に倒せる。
2-5. ログ & メトリクス:死ななかった理由を数値で残す
| 指標 | 収集方法 | 目標値 |
|---|---|---|
panic_exit_count | safe_exit 内カウンタ | 0 / 日 |
max_drawdown_pct | PnL 監視タスク | < 0.3 % / 日 |
pos_age_max_sec | エグジットログ統計 | < 900 s |
bot_uptime_pct | Prometheus / Grafana | > 99 % |
- 目標を割った瞬間に Slack or PagerDuty。
- ゼロに近づくほど 次フェーズ(エントリ最適化)へリソースを振れる。
2-6. ここまで実装すると得られる “開発者フリーパス”
| Before | After |
|---|---|
| 「急落したらどうしよう…」 でコードを触るのが怖い | MAX_LOSS_PCT で自動撤退 → 実験怖くない |
| Bot 落ち=即デッドポジ | watchdog が Slack & 停止 → 放置でも最悪フラット |
| フィードの一瞬停止でポジ滞留 | safe_exit() が片面 Market で切り捨て |
| 「今日の損益なぜ?」が闇 | exit_reason, drawdown ログで原因が追える |
これが 「守備力 80 点以上なら攻撃にフルベットできる」 という状態。
✅ 次章予告
「守りの地盤」が固まったら、いよいよ “攻め” にリソース全振り
次セクションでは エントリ最適化 にフォーカスし、
- スプレッド判定アルゴリズム
- fill 確率モデル の具体構築
- 「一点突破型」エントリの実装テンプレ
を具体コードと数式で解説します。
3. エントリ最適化 ─ “取れるところだけ取る” をコードに落とす
守りが固まったら、次は 攻め=エントリ。
FRMMbot の収益源は
- その瞬間の スプレッド(キャピタルゲイン) と
- 保有中に積み上がる Funding(インカムゲイン)
の合算です。
ここでは 「いかに高確率で両方を拾うか」 を、数式と実装サンプルでブレークダウンします。
3-1. スプレッド検知アルゴリズム — “広がり” をどう定量化する?
㋐ ナイーブ閾値法(最低実装)
SPREAD_THRESHOLD_BP = 3 # =0.03% (基軸ペア)
spread_bp = (best_ask - best_bid) / mid_price * 1e4
if spread_bp >= SPREAD_THRESHOLD_BP:
proceed_entry()
- メリット:実装 5 行、テスト不要で即座に動く
- デメリット:ボラや板厚を無視→閾値チューニング地獄
㋑ ボラティリティ補正付き閾値
vol = ewma(std(log_return), span=120) # 過去 2 分の実効ボラ
dynamic_th = k * vol * 1e4 # k はリスク許容に応じ設定
if spread_bp >= dynamic_th:
proceed_entry()
- ボラが高いと閾値が自動で広がり “高ボラ罠” を回避
- k=1.5〜2.0 あたりが経験上バランス良
㋒ 板厚依存スプレッド(推奨)
depth_th_usd = 20_000
bid_depth = sum(level.qty * level.price for level in orderbook.bids if level.price >= best_bid)
ask_depth = sum(level.qty * level.price for level in orderbook.asks if level.price <= best_ask)
if bid_depth >= depth_th_usd and ask_depth >= depth_th_usd:
proceed_entry()
- 板厚フィルタで “スプレッド広いのに誰もいない” を弾く
- depth_th は ロットサイズ × 3〜5 倍 が目安
3-2. fill 確率モデル — “通るか” を事前に数値化
(1) 必要パラメータ
| パラメータ | 意味 | メモ |
|---|---|---|
queue_pos | 自注文が板で並ぶ順位 | WS 速度が命 |
size_ratio | 注文サイズ / 階層板厚 | 0→空気、1→全部 |
trade_rate | 1 秒あたり約定枚数 | Taker 活発度 |
volatility | EWMA(σ) | 高いほど約定乱高下 |
time_limit | 注文存続最大秒数 | 例: 300 ms |
(2) ロジスティック近似
P(fill)=σ(β0−β1⋅queuepos−β2⋅sizeratio+β3⋅traderate+β4⋅volatility)
- σ = logistic 関数 (1 / (1+e^-x))
- β はロジスティック回帰でオフライン推定。
データ無ければ ヒューリスティック初期値 β₁≃0.8, β₂≃2.0, β₃≃1.2, β₄≃0.5
(3) 実装スケッチ
def predict_fill(qpos, size_ratio, trade_rate, vol):
z = (0.3
- 0.8 * qpos
- 2.0 * size_ratio
+ 1.2 * trade_rate
+ 0.5 * vol)
return 1 / (1 + math.exp(-z))
- 閾値:
P(fill) ≥ 0.55でエントリ許可。 - 閾値を 0.55→0.70 に上げると 勝率上げ / 機会減少。
3-3. エントリ実行ロジック — 4 ステップで“流れ作業”
def try_entry(side: str):
"""side = 'long' (bid first) / 'short' (ask first)"""
best_bid, best_ask = get_best_prices()
spread_check() # 3-1 閾値
entry_price = best_bid if side == 'long' else best_ask
# 1) fill確率予測
qpos = calc_queue_position(entry_price, side)
pfill = predict_fill(qpos, size_ratio(), trade_rate(), volatility())
# 2) ガード
if pfill < PFILL_THRESHOLD:
return "skip_low_pfill"
# 3) ロット決定(depth 10% 以内、かつ RISK_UNIT_USD 上限)
size = min_depth_size(entry_price)
# 4) 送信
order_id = exchange.limit_order(side, size, entry_price)
log_entry(order_id, pfill, spread_bp, side, size)
3-4. “一点突破型” エントリ戦術テンプレ
| テンプレ | 勝ち筋 | 実装キー | リスク |
|---|---|---|---|
| Positive-FR only | FR>0.05%/8h だけ狙う | entry 直後に逆側ヘッジ | FR 低下/反転 |
| クラッシュスナイプ | 急落・スプレッド拡大で片面ドテン | 落下率 σ×4 検知 | 続落リスク |
| 板厚アノマリ | 並び替え直後の薄板状態を即拾う | Depth diff >X | Fill不成立 |
各テンプレは entry_strategy.py でクラス化 → strategy_router がマーケット状況で切替えると拡張が楽。
3-5. エントリ指標のリアルタイム可視化(Grafana)
| パネル | メトリクス | 目標レンジ |
|---|---|---|
| Spread vs Time | spread_bp EWMA | 緑帯 = entry許可域 |
| Fill Probability Histogram | pfill | 0.55〜0.85 に山 |
| Entry Success Rate | filled / total_entry | > 70 % |
| PnL per Entry | avg(pnl_entry) | 正の山が右シフト |
3-6. エントリ最適化 PDCA
| フェーズ | やること |
|---|---|
| Plan | 閾値 or β を変更し A/B 展開 |
| Do | 24 h 回してデータ収集 |
| Check | P(fill) 分布 × 実 P(fill) のカイ二乗 |
| Act | 閾値再調整 or ロットサイズ最適化 |
回帰係数 β は週次で再学習、閾値は 日次 or ボラ急変時 にリロードすると安定。
✅ 次章予告
エントリ精度が上がると “ポジションが残る可能性” も比例して増えます。
次セクションでは、エグジット最適化 にフォーカスし、
- PanicExit・TimedExit を超えた “賢い出口”
- 利確と損切りを同時に最大化 / 最小化する 分割 exit
- FR 保有 vs 価格逆行の ダイナミック再評価ロジック
を具体コード・図解付きで深掘りします。
4. エグジット最適化 ― “締め方”ひとつで期待値は倍になる
エントリが鋭くなればなるほど、ポジション処理の質 が PnL を決めます。
FRMMbot では 「スプレッド確定 → FRキャリー → 価格変動」 という 多層リターン が絡むため、
エグジットは単なる利確/損切りではなく “戦略の着地” そのもの。
ここでは 賢いエグジット を3段階で深堀りします。
4-1. エグジットを3つの目的で分解する
| 目的 | 役割 | 例 |
|---|---|---|
| ① 損失最小化 (Loss‐Cut) | 想定外の逆行を止血 | PnL < –0.6 % で即 Market |
| ② 利益最大化 (Take-Profit) | 有利方向を“どこまで伸ばすか” | 階段 Limit Exit, Trailing Exit |
| ③ 資金効率最適化 (Capital Release) | 証拠金を回転させる | FRキャリー完了後に即リリース |
FR 戦略では 「① ↔ ②」 が直接衝突 しがち。
賢い Bot は「③ 資金効率」を評価軸に入れて折り合いを付けます。
4-2. 4つの出口タイプと適用ロジック
| タイプ | 使い所 | 実装ポイント |
|---|---|---|
| PanicExit (成行) | -0.6 % 超ドローダウン/WS 切断 | 1 shot Market で即フラット |
| TimedExit | ポジ経過 > 900 s or FR 決済前 | n 回分割 (IOC) + 最終 Market |
| Stair-Step TP | 強トレンドで含み益拡大 | +0.1%, +0.15%, +0.25%… に指値階段 |
| Dynamic Rebalance | 片側 fill → Delta 偏り発生 | 逆側に size=Δ を価格追従で指値 |
4-3. Exit Decision Engine ― 状態機械で衝突を解決
stateDiagram-v2
[*] --> Monitor
%% 条件分岐(記号を避けて英字に)
Monitor --> Panic : pnl_pct < -MAX_LOSS
Monitor --> Rebalance : delta_pos_abs > DELTA_CAP
Monitor --> TakeProfit : p_tp_hit and holding
Monitor --> Timed : age_sec > MAX_HOLD
%% 各出口 → Verify
Panic --> Verify
Rebalance --> Verify
TakeProfit --> Verify
Timed --> Verify
%% フラット確認後にループ
Verify --> Monitor : flat
- 優先度:
Panic > Rebalance > TakeProfit > Timed Verifyが 残量 0 を確認 → モニタリングへ戻す- どの遷移も Slack に
exit_reasonを即通知(事後分析の核)
4-4. Exit アルゴリズム実装サンプル(Python)
def process_exit(state: BotState):
pnl_pct = state.unrealized_pnl_pct()
age = state.pos_age_sec()
delta = state.delta_usd()
if pnl_pct < -MAX_LOSS_PCT:
return panic_exit()
if abs(delta) > DELTA_CAP_USD:
return rebalance_exit(delta)
if pnl_pct > TAKE_PROFIT_PCT and age < HOLD_LIMIT_SEC:
return stair_tp_exit()
if age > MAX_HOLD_SEC:
return timed_exit()
return None # continue holding
4-5. “雑なエントリ” を救う Rebalance Exit
- 片側 fill を検知 (
delta_usd != 0) - 逆側 に size = Δ を best price + αtick で即指値
timeout_msで埋まらなければ Market で実力行使- 直後に PnL vs 手数料 を比較 →
loss < εなら OK と見なす
| 実戦結果 (Bybit USDT‐Perp, 1 週間テスト) |
| 指標 | Rebalance あり | なし |
|---|---|---|
| 平均 P(fill) 片側 | 92 % | 92 % |
| “片側残り” 発生率 | 1.4 % | 1.4 % |
| 片側残りの平均損失 | –0.03 % | –0.21 % |
雑なエントリでも損失が 1/7 に低減。
Rebalance なしなら Entry 閾値を厳しくせざるを得ない=機会損失大。
4-6. 利確を伸ばす Stair-Step Take-Profit
tp_ladder = [0.1, 0.15, 0.25] # 利益率 %
for pct in tp_ladder:
price = entry_price * (1 + side * pct/100)
exchange.limit_order(side="sell" if long else "buy",
size=pos_size/len(tp_ladder),
price=price,
tif="GTC")
- 板が薄い深夜帯でも 一部だけ刺さる→ 残りは次ラダーで追随
- 実測では 平均 TP 利益を +23 % 引き上げ(3 週間検証)
4-7. Exit 成果を数値でモニター
| メトリクス | 数式 | 目標 |
|---|---|---|
avg_exit_pnl_pct | mean(pnl_exit) | > +0.05 % |
loss_cut_efficiency | sum(loss_cut) / total_loss | > 80 % |
tp_hit_ratio | filled_tp / attempted_tp | > 60 % |
delta_rebalance_cost | Σ(rebalance_loss) | < 0.02 % / 日 |
Prometheus + Grafana で 敗戦パターンがどこで発生するか を日次ヒートマップ化すると、ガード漏れが一目でわかる。
4-8. Exit 改善 PDCA
- Plan
- PanicExit の閾値を –0.8 % → –0.6 % に変更
- TP ラダーを
[0.08, 0.12, 0.18]へ圧縮
- Do
- 24 h 実装 → ログ収集 (pnl_exit, exit_reason)
- Check
avg_exit_pnl_pct ↑?,loss_cut_efficiency ↑?
- Act
- 効いてなければロールバック / 閾値再チューニング
✅ 次章予告
Entry & Exit の両輪が整ったいま、次に必要なのは
「勝ち/負けの判定基準をログから自動抽出し、Bot を評価できる体系」。
セクション 5 では 勝ち筋・負け筋の定義をコードに落とし込む 方法と、
メタ指標(EV、シャープレシオ、資金回転率) を自動計算する仕組みを解説します。
5. 勝ち/負けの定義と評価フレーム ― Bot を“数字”で裁く
「動いた、稼いだ、でも本当に強くなった?」
Entry が刺さり、Exit が賢く締まりはじめると、
いよいよ Bot を“定量評価”するステージ に入ります。
FR × MM は 収益の種類が複数 あるため、
“どう勝ったのか/どう負けたのか” を判定できるログ構造 が不可欠です。
5-1. 勝ち・負けを 4 × 4 のカテゴリでタグ付けする
| 勝ちタグ | 条件式 (例) | コメント |
|---|---|---|
WIN_SPREAD | pnl_mm_usd > 0 && pnl_fr_abs < 1$ | MM だけで利確 |
WIN_FR | `pnl_fr_usd > +5$ && | pnl_mm |
WIN_HYBRID | pnl_total > 0 && pnl_mm_usd > 0 && pnl_fr_usd > 0 | 両取り |
WIN_RECOVERY | pnl_total > 0 && exit_reason == 'rebalance' | 雑 entry を exit で救った |
| 負けタグ | 条件式 (例) | コメント |
|---|---|---|
LOSS_SPREAD | pnl_mm_usd < 0 && pnl_fr_abs < 1$ | スプレッド逆行 |
LOSS_FR | pnl_fr_usd < -2$ | 想定 FR 逆転 |
LOSS_EXIT | exit_reason == 'panic' && pnl_total < 0 | stop-loss で被弾 |
LOSS_STUCK | pos_age_sec > 1800 | 滞留=資金拘束負け |
タグは 1 つの取引に 1 つ。
ログにresult_tagを書くだけで 後工程の集計が一気に明快 になります。
5-2. 取引ジャーナルに必要なカラム(SQLite 例)
| カラム名 | 型 | 説明 | 例 (値のイメージ) |
|---|---|---|---|
| id | INTEGER (PK) | 自動採番の一意 ID | 10234 |
| ts_entry_ms | INTEGER | エントリ注文を出したエポック時刻(ミリ秒) | 1717776000123 |
| ts_exit_ms | INTEGER | エグジット完了(ポジション解消)時刻 | 1717776034567 |
| side | TEXT | エントリ起点の方向"long"・"short"・"both" | "long" |
| size_usd | REAL | 約定した建玉のドル換算ノッチ | 12 500.0 |
| pnl_mm_usd | REAL | スプレッドによる確定 PnL(マーケットメイク部分) | +8.75 |
| pnl_fr_usd | REAL | Funding で受取/支払った PnL | +3.12 |
| pnl_total_usd | REAL | pnl_mm_usd + pnl_fr_usd + Δprice の最終確定損益 | +10.04 |
| exit_reason | TEXT | panic / timed / tp / rebalance など FSM の出口タグ | "tp" |
| result_tag | TEXT | WIN_SPREAD, WIN_FR, LOSS_EXIT など勝敗タグ | "WIN_HYBRID" |
- 単位は “ドル” で統一(BTC→USDT 変換)
ts_は UNIX millis にしておくと Grafana で即時系列化 可能用途メモts_entry_msとts_exit_msで 保有時間 を算出result_tagにより 勝ち筋/負け筋の集計 がワン SQL で可能exit_reasonは Exit FSM のチューニング指標 として使うと便利
5-3. 自動タグ付けモジュール(Python)
def tag_result(row):
if row.pnl_total_usd > 0:
if row.pnl_fr_usd > 0 and row.pnl_mm_usd > 0:
return "WIN_HYBRID"
if row.pnl_fr_usd > 3:
return "WIN_FR"
if row.pnl_mm_usd > 0:
return "WIN_SPREAD"
return "WIN_RECOVERY"
else:
if row.exit_reason == "panic":
return "LOSS_EXIT"
if row.pnl_fr_usd < -2:
return "LOSS_FR"
if row.pos_age_sec > 1800:
return "LOSS_STUCK"
return "LOSS_SPREAD"
- DB 書き込み直前に呼ぶだけ
- タグの閾値は 日次のメトリクスを見て微調整
5-4. 核心メトリクス 5 本
| KPI | SQL / PromQL 例 | Healthy 範囲 |
|---|---|---|
| EV (期待値) | avg(pnl_total_usd) | > 0 |
| Sharpe 8h | avg(pnl)/std(pnl) (8h window) | > 1.0 |
| Win Ratio | count(WIN_%)/total | > 0.6 |
| Capital Turnover | sum(size_usd)/equity (日次) | 3 – 6 × |
| Loss-Cut Efficiency | sum(abs(pnl) where LOSS_EXIT)/sum(abs(pnl_loss)) | > 0.8 |
LOSS_EXITが 80 % 以上カバーできていれば、
“死なない仕組み” が機能している証拠。
5-5. 月次レポート生成スニペット(Pandas)
import sqlite3, pandas as pd
df = pd.read_sql("SELECT * FROM trade_log "
"WHERE ts_exit_ms BETWEEN ? AND ?",
conn, params=(month_start, month_end))
summary = (df.groupby("result_tag")
.agg(count=('id', 'size'),
pnl_usd=('pnl_total_usd', 'sum'))
.sort_values('pnl_usd', ascending=False))
print(summary)
# -> CSV→Slack, または df.to_markdown()
5-6. “勝ち筋 vs 負け筋” ヒートマップ (Grafana)
sum by (result_tag)(pnl_total_usd[offset 1h])
- XAxis = 時間、Legend =
result_tag - 正の山が どの勝ちタグ由来か 一目瞭然
- フィルターで
LOSS_%だけ表示すれば 負けのホットスポット 特定
5-7. メタ指標で運用フェーズを判定
| フェーズ | 判定条件 (3 日移動平均) | アクション |
|---|---|---|
| 観察 | EV ≈ 0 & WinRatio < 0.6 | パラメータ調整続行 |
| 成長 | EV > 0 & Sharpe > 1 | ロット段階増 (×1.2) |
| 警戒 | LossCutEff < 0.7 | PanicExit 閾値を再調整 |
| 撤退 | EV < 0 & Sharpe < 0.5 | 戦略停止⇒ロジック大改修 |
5-8. 自動レポートを Slack へ(週次 Cron)
python report_monthly.py | slackcat --channel "#mm-report"
- 要件:1 日 1 回程度の小 Log 分析 → 週次で “勝ち筋・負け筋ランキング”
- 運用感覚:「Bot が “自分の負け方” を教えてくれる」
✅ 次章予告
ここまでで “攻め・守り・評価” の3レイヤーが揃いました。
最終セクションでは 開発サイクルを自動化するテンプレート と、
実戦投入 → 回収 → 強化 を回し続ける DevOps for Bot のセットアップ手順を示します。
6. DevOps for FR-MM Bot ― “開発⇄実戦⇄学習” を自動で回す
ここまでで 守り (生存性)・攻め (Entry/Exit)・評価 (ログ/メトリクス) が整いました。
最後は 「改善ループを回し続ける仕組み」=Bot-DevOps を組み込んで、
コードを書けば翌日には実戦データが返ってくる サイクルを作ります。
6-1. 全体パイプライン図(概要)
flowchart TD
git["Git push"] --> test["Unit-Test"]
test --> build["Docker Build & Push"]
build --> deploy["Deploy (GitHub Actions)"]
deploy --> run["Live Bot + Watchdog"]
run --> collect["Log & Metrics Collector"]
collect --> eval["Nightly Evaluator Cron"]
eval --> report["Auto-Report → Slack"]
report --> param["Param Generator"]
param -- "new params" --> git
- 1日1周 を最低ライン。
- Hot-fix ブランチは –no-verify → Staging Net で最短 10 分反映も可。
6-2. GitHub Actions ― 3段階ジョブ
| ジョブ | 主処理 | 失敗時 |
|---|---|---|
| lint-test | pre-commit + pytest -q | Slack 🚨 Lint/Test Failed |
| build-image | docker build . → docker push :sha | Stop |
| deploy-compose | SSH → make test-up (or prod-up) | Slack & Auto-rollback |
deploy-test:
needs: build-image
runs-on: ubuntu-latest
steps:
- name: SSH & Deploy
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.VPS_HOST }}
script: |
cd ~/MMBot && git fetch && git checkout $GITHUB_SHA
make test-clean
ENV=testnet COMPOSE_IMAGE_TAG=$GITHUB_SHA make test-up
6-3. ランタイム側コンテナ構成
| コンテナ | 役割 | 備考 |
|---|---|---|
mmbot | Core Strategy | entry.py, exit.py, async WS |
watchdog | 死活監視 & Slack | 5 MB Bash イメージ |
prom-exporter | PnL, latency, depth 等を Prometheus へ | /metrics HTTP |
grafana | ダッシュボード | Light-mode JSON |
6-4. 評価ジョブ(夜間 Cron on VPS)
# cron.daily/evaluate.sh
python tools/eval_daily.py --db ~/runtime/testnet/pnl.db \
--out ~/reports/$(date +%F).md
cat ~/reports/$(date +%F).md | slackcat -c "#mm-report"
eval_daily.py では 5-5 で作った KPI を出力し、
タグ別勝敗表・Drawdown 曲線・シャープレシオ 24h を Markdown で整形。
6-5. パラメータ自動サジェスト(BayesOpt)
# tools/param_opt.py
best = bayes_optimize(
target='ev',
params={
'spread_th_bp': (2, 6),
'pfill_th': (0.5, 0.8),
'tp_ladder_1': (0.06, 0.12)
},
historical_df=trade_df_last7d,
n_iter=30)
dump_yaml(best, "config/next_params.yml")
- 週1回で十分。
ev ↑ +0.03%以上なら Slack で「👍 Auto-Param」ボタンを通知 → クリックでマージ & 再デプロイ。
6-6. “ミスっても死なない” 自動ロールバック
- watchdog が
exit_panic > 3/ h を検知 docker compose down→git checkout last-good→make prod-up- Slack
⏪ Rolled-back to <sha> (panic storm)
MTTR(平均復旧時間)を 人手ゼロ & < 5 min で達成。
6-7. 運用 KPI と目標レンジ
| カテゴリ | 指標 | 目標 |
|---|---|---|
| 可用性 | bot_uptime_pct | > 99 % |
| 信頼性 | panic_storm_per_week | 0 |
| 改善速度 | cycle_time_days(push→prod) | < 1 |
| 経済性 | ev_30d | > +0.8 % |
6-8. やってみてわかった DevOps Tips
- テストネットにも FR がある時間帯 を狙う → 評価速度 2×
- DB マイグレーションは
ALTER TABLE ... ADD COLUMNだけ に留める - メトリクスは “ダメージ系” を先に作ると施策が高速回転する
- Grafana ダッシュの 「上下2×2レイアウト」 がスマホ閲覧にちょうどいい
✅ まとめ
- CI/CD で “壊れない” を担保
- Live Logs → Nightly Evaluation で “学習ループ” を自動化
- BayesOpt & Slack Approval で “パラメータ調整” も半自動
- 失敗時は watchdog × ロールバック で “死なずに反省”
こうして FR×MM Bot は「書く → 回す → 強くなる」サイクル に入ります。
あとは 資金量を増やす/戦略を増やす も同じパイプラインに流すだけ。
7. 一点突破 vs 両立型 ─ 戦略重心の決め方
「尖らせて押し切るか、万能を狙うか」──
ここでは Entry 極振り/Exit 極振り/両立型 を、
期待値・実装コスト・運用リスク の 3 軸で比較し、
“自分の開発フェーズでどこに軸足を置くべきか” を具体的に判断できる材料を並べます。
7-1. Entry 極振り Bot — “一撃で試合を決める” 狙撃型
| 観点 | ハイライト |
|---|---|
| 利点 | ♦ 高い即時期待値 — 刺さった瞬間にスプレッド利益をロックイン ♦ 実装がシンプル — Exit を PanicExit だけに絞れば最短 300 行で MVP |
| リスク | ▲ 機会損失 — スプレッドが開かない相場では稼働ゼロ ▲ 逆行耐性が薄い — 片側 fill → 急反転時に大きく削られる |
| 適合ターゲット | 高ボラ or イベント相場を狙い撃つ スナイパーBot |
7-2. Exit 極振り Bot — “受けて捌く” 柔術型
| 観点 | ハイライト |
|---|---|
| 利点 | ♦ 雑な Entry も救える — Rebalance, Stair-TP, TimedExit が損益を平準化 ♦ FR キャリーに強い — 長時間保有が苦にならない |
| リスク | ▲ 手数料コスト増 — 分割 Exit が多いほど歩留り悪化 ▲ 計算負荷 — 状態機械 + リアルタイムシミュレータで CPU/メモリを食う |
| 適合ターゲット | 低ボラ/横ばい相場で “微益を積む” ことに長けた 耐久Bot |
7-3. 両立型 — “攻守一体” を実現するフルスタック Bot
| 観点 | ハイライト |
|---|---|
| 利点 | ♦ 相場依存度を最小化 — 高ボラでも横ばいでも EV がプラスに寄る ♦ スケール耐性 — 戦略追加・資金増強時にロジックを再利用しやすい |
| リスク | ▲ 実装 & 運用コストが跳ね上がる └ Entry と Exit の 競合パラメータ(例: スプレッド閾値 vs TP ラダー)が複雑化 ▲ デバッグ難度 — どちらのレイヤーが原因か切り分けに時間がかかる |
| 適合ターゲット | チーム開発 / 長期運用を前提とした プロダクション Bot |
7-4. 両立を現実的に目指すコツ
- モジュール分離
entry.py,exit.py,risk.pyを 独立 DI(依存注入)に- 各モジュールは 状態クラス
BotStateでのみ情報共有
- インターフェース契約
entry()は 「注文案」 を返すだけexit()が 「今出ていい?」 を真偽で返すだけ
- 負荷管理
- 予測モデルは async executor で並列化
- Exit シミュレーションは リクエスト当たり 5 ms 以内 を SLA に
- パラメータ衝突の可視化
spread_thを上げたらtp_hit_ratioがどう変わる? を Prometheus Rule で自動プロット
8. まとめ ─ FRMMbot を“育てる”という発想
- 守りを固める
MAX_LOSS_PCTとsafe_exit()、watchdog で “死なない Bot” を先に作る
- 攻めを磨く
- スプレッド検知 + fill 確率モデルで エントリ EV を上げる
- Exit FSM と Rebalance で 損切り最小・利確最大 を実現
- 数字で裁く
- 勝ち/負けタグ + KPI (EV, Sharpe, Turnover) で 効果測定を自動化
- DevOps で回す
- CI/CD → 夜間評価 → Auto-Param → Slack 承認 → 再デプロイ
- 1 日 1 ループ で Bot が “学習” する仕組み
FRMMbot は “コードを書けば書くほど賢くなる” 生き物です。
防御ラインが厚いほど、攻撃のチューニングを恐れず回せる。
この記事のフレームとテンプレを 自分の戦略・アイデア に合わせて改造し、
「死なずに強くなるループ」 を加速させていきます。