Bot 開発ログ

🛠️開発記録#289(2025/8/26)本日の開発ログ

今日は実弾で回し始めたスイング系botのみ開発ログを掲載します。現在、実弾で回しながら追加で必要な機能を積んでいる段階です。

MMbotなどもいじっていましたが、特に新しいことはしていないので開発ログは割愛します。

スイング系bot開発ログ

タイムライン

0) キックオフ(Python → Rust 移行)

  • 目的:国内CEX(bitbank) の現物スイングBotを実弾運用できる形で構築。
  • 初期版:Python+ccxtで雛形 → Rust単一バイナリに移植(堅牢性・速度・運用性優先)。

1) データ取得まわりの安定化

  • candlestickの10000エラー発生:URL粒度の誤りが原因。
    • 修正:pair=btc_jpy(アンダースコア)、1hour=YYYYMMDD4hour/1day=YYYY完全準拠
  • 取得戦略刷新:今日→過去へのバックワード走査、MIN_BARS 到達で即停止
  • パースの堅牢化:
    • 文字列/数値両対応len<6/invalid ts/vol=0安全スキップtypeeq_ignore_ascii_case("1hour")
  • 取得失敗時はフォワード走査にフォールバック、RateLimiter指数バックオフ実装。

2) Private API 認証(20001)切り分け → 解決

  • 最小疎通テスト /v1/user/assetsHTTP200 & success==1で合格判定。
  • 署名仕様をユーティリティに集約
    • GET:nonce + path (+ "?" + query)
    • POST:nonce + body_json(送るJSON文字列そのもの)
    • HMAC-SHA256 → Base64hex は廃止)。
  • next_nonce()絶対単調増加に(ms衝突をAtomicU64で回避)。
  • .bitbank_secrets厳密トリム(前後空白/改行/\r除去)。
  • 起動時のプリフライトを1本に絞り(/assetsのみ)、nonce連打による誤検知を回避。

3) 売買ロジック・実弾フロー実装

  • Donchian×ATR(4h/1h相当)
    • ENTRY:上抜け(price >= hi)、リスク基準サイズ
    • EXIT:トレーリングストップstop=max(stop, lo))、Marketクローズ
  • 手数料の扱い
    • 注文価格には手数料を乗せない(tick丸めの素の価格)。
    • PnLは両側手数料込み((exit*(1-fee)) - (entry*(1+fee))) * qty
  • 丸め/最小ノーション:BTC 8桁、JPY 0桁、例:1000円以上のみ発注。
  • Hard日次停止Rloss <= -5 で翌日0:00まで新規停止。
  • 会計方針の統一:実弾でも約定時に equity を動かさない(EXITでPnL反映=紙と一致)。

4) 注文管理(Order/Status/Cancel)

  • place_spot_order():Market/Limit両対応(IOC寄り指値も可)。
  • get_order_status()
    • active_orders?pair=btc_jpy&count=100署名は query 含む
    • 見つからない場合は /v1/user/orders で履歴確認 → 不明は "unknown" で返し、positionを触らない
  • cancel_order():冪等に実装。

5) 競合/停止対策(非同期ロック設計)

  • ロック中に .await しないパターンへ全面移行:
    1. state から必要情報をスナップショット
    2. ロック解放→HTTP実行
    3. 再ロックして結果を反映
  • ENTRY/EXIT/ステータス照会の全ルートで適用

6) 運用整備

  • Makeコマンドで起動/停止/ログ/ビルドを統合。
  • 起動時プリフライト:Public/Private“緑化”
  • 1行サマリ [SUM] ts=… p=… hi=… lo=… atr=… pos=… Rloss=…[HB]視認性向上。

2. 技術トピック別まとめ

2.1 署名・認証

  • GET:nonce + path (+ "?" + query)、POST:nonce + body_json
  • Base64 のみ(hexはサポートしない)
  • path/v1/... 固定(ホスト無し・末尾スラ無し・空白無し)
  • next_nonce() は ms+Atomic で同ms衝突でも必ず+1して単調増加。

2.2 データ取得

  • URL粒度:btc_jpy / 1hour=YYYYMMDD(4h/1d=YYYY)
  • 取得:今日→過去MIN_BARS到達で即終了、失敗時はフォワードにフォールバック
  • パース:文字列/数値両対応len<6/invalid ts/vol=0 はスキップ
  • type 比較は eq_ignore_ascii_case("1hour")
  • RateLimiter+指数バックオフ

2.3 戦略/リスク/会計

  • Donchian(N=20)× ATR(N=14, Wilder)
  • ENTRY:上抜け、リスク基準のポジションサイズ
  • EXIT:トレーリング(lo)Marketクローズ
  • PnL:両側手数料込み
  • 会計:約定時に equity を動かさない、EXITでPnL反映
  • Hard停止:Rloss <= -5(ENTRY前にチェック)
  • enter_allowed():停止中 / Rloss<=-3 などでブロック
  • 最小ノーション・精度チェック(誤発注防止)

2.4 注文・照会・取消

  • 発注:Market / 攻撃的IOC指値(best_ask+1tick相当)を推奨
  • 照会:active_orders(pair付き)→orders(履歴)→unknownposition無変更
  • 取消:POST で冪等に

2.5 非同期・ロック設計

  • スナップショット→HTTP→反映。ロック中await禁止
  • すべてのHTTPパスにRateLimiterを適用(インスタンス使い回し)

2.6 ログ/監視

  • 起動時:/v1/user/assets だけを1回実行してsuccess==1を確認
  • 運用時:[HB](心拍)と [SUM](要約)で状況把握
  • 秘密情報・署名値はログに出さない(長さのみ)

3. 現行仕様(要約)

  • 取引所:bitbank(現物)
  • 銘柄BTC/JPY(内部表現:btc_jpy
  • 時間足:取得は1h(内部で4h合成可)
  • 戦略:Donchian上抜けで買い、Donchian下限でトレーリング→ヒットで売り
  • リスクrisk_per_trade%×equity、ストップ距離=stop_mult×ATR、最小ノーション=1000円(例)
  • 注文:BUY=Market/IOC寄り指値、SELL=Market(確実にクローズ)
  • 会計:PnLは両側手数料込み、EXITで反映
  • 停止Rloss <= -5 で翌日まで新規停止
  • 状態管理position/entry/stop/equity_at_entry/order_id/order_status/last_atr/...
  • 署名:HMAC-SHA256 → Base64、nonce は絶対単調

4. 運用手順(Runbook)

  1. プリフライト
    • make buildmake run
    • 起動時ログで /v1/user/assetssuccess==1 を確認(1回だけ実行)
    • Public candlestick で直近バーが取得できていることを確認
  2. 最小カナリア(0.1R)
    • BUY:Market もしくは IOC攻撃的指値
    • 約定確認:REST 2–3回(合計≤10s)。のち WS に移行
    • SELL:Market でクローズ
    • [SUM][EXIT]/[SELL_LIVE]/[SELL_FAIL] を確認
  3. 段階昇格
    • 0.1R → 0.5R → 1.0R(Hard停止ONのまま)
    • ボラ/スプレッドが極端に悪い時間帯は IOC で滑り抑制
  4. 停止/再開
    • make stop → state保存完了をログで確認
    • 再開:make run(プリフライトが再実施される)

5. 既知の制約 / Backlog

  • Private WebSocket 未導入
    • 今は REST 補助で十分動くが、filled/partial/canceled/asset change即時反映はWSが最適。
  • 取引所ごとの精度/最小ノーションAPIで動的取得に(今は固定閾値)。
  • 部分約定のポジション反映はログのみ → 将来的に加重平均/残量反映へ拡張。
  • /metrics(Prometheus)で可視化:
    • bars_fetched_total, api_retry_total{endpoint}, entries_total, exits_total, loop_latency_ms

6. 現状評価

  • 本番稼働可能(カナリア→段階昇格の手順で)。
  • 署名・nonce・URL・ロック・PnL・トレーリング・発注/照会/取消 まで、実弾で必要な最低限を満たす
  • 重大リスクは解消済み(ロック中await、ストップ未設定、SELL失敗時のpositionクリアなど)。

スイング系bot:今後やること

P0(即効・1〜2日)── 誤トレード減らす/取りこぼし減らす

1) フィルターで“ダマシ”を間引く

  • ボラフィルター:ATRが閾値未満ならENTRY禁止
if atr_v < k_vol * px_tick { continue; }  // k_vol は 2〜5 tick 相当
  • スプレッド/板厚チェック(WSがなければRESTのbest bid/askを直近で保存):
    スプレッドが ≤ spread_max_bps の時だけ発注。板の一段目数量が qty を十分満たすか確認。
  • 時間帯フィルター:板が薄い時間(例:深夜・早朝)はENTRY禁止にするフラグを導入。
    trade_window = [09:00–25:00 JST] など。

2) トレーリングを“守り寄り”に

  • いまは Donchian下限で追従。Chandelier Exitstop = max(stop, Hn - m*ATR))も選択可能にするフラグを追加。 トレンド継続時の利益保持を安定化。
let chand = highest_high_last_n - m * atr_v;
st.stop_price = Some(st.stop_price.unwrap_or(chand).max(chand).max(lo_v));

3) 発注の“滑り・未約定”ガード

  • IOC指値→未約定→即Marketの二段構えを実装(1回限り・≤1s)。
// 送ったlimitが active → cancel → market へ
  • 最大滑りBPS(fill_px - signal_px)/signal_px > slippage_max_bps ならカウント&Slack通知。複数回連続で停止フラグ(運用保険)。

P1(短期・1週間)── 期待値を押し上げる

4) 部分利確+追従(分割EXIT)

  • qty50% を +1R で利確、残りをトレーリング。
let tp = entry + 1.0 * cfg.stop_mult * atr_entry;  // 1R
  • 実装:state.partial_taken: booltp_price: f64 を持つ。tpヒットで半分SELL→stopは据え置き。

5) MTF(マルチタイムフレーム)整合

  • ENTRYは1h、4hトレンド(Donchian上昇)と整合している時のみ許可。
    → ダマシ激減。4hのhi/loを同時計算 or キャッシュ。

6) パラメータ安定化の軽量探索

  • DON={16,20,24} × STOP_MULT={1.5,2.0,2.5,3.0}直近90日ローリングで紙検証 → 同点なら保守的な方を採用。
  • 比較指標:MAR(CAGR/MaxDD)Expectancy/Trade(R)HitRate
  • 実装:バックテストを実装と同一ロジックで1ファイル簡易化(将来の本格バックテスタの布石)。

P2(中期・2〜4週間)── 実装負担はあるがリフト大

7) Private WebSocket で即時反映

  • order/asset change チャンネル購読:filled/partial/canceled が即取れる。
  • RESTは補助(≤10s)に格下げ。見つからない=unknownは据え置き。

8) 発注ロジックのスプレッド自覚化

  • WSで best bid/ask を常時保持。
  • ENTRYask + tick * n でIOC、nspread/avg_spread の関数に。
  • SELL:Market優先だが、板厚が十分なら bid - tick * n IOC → 未約定ならMarket。

9) 風向き判定(レジーム)と“取引停止モード”

  • ADX(14) or ReturnのHurst/チャープトレンド相/レンジ相 をざっくり分類。
    レンジ相はDonchianのブレイクを抑制(ボラ・スプレッド比が悪い時)。
  • 自動停止モード
    連続の SELL_FAIL ≥ N20001/429バーストbars欠損が起きたら自動で paper に降格+Slack通知。

可観測性・運用(並行して入れる)

10) /metrics(Prometheus)最小

  • entries_total, exits_total, sell_fail_total, api_retry_total{endpoint}, bars_fetched_total, loop_latency_ms, equity, r_loss
    異常の見える化効果測定が一気に速くなる。

11) 監視・通知

  • Slack/Discord で
    • [ALERT] SELL_FAIL order_id=...
    • [HALT] Rloss<=-5
    • [AUTH] 20001
    • [DATA] no bars
      → 問題の初動が秒でできる。

研究と検証(裏で回す)

12) イベント駆動の同一ロジック・バックテスト

  • 現行ロジックをそのまま使い、CSVのOHLCVで再生。手数料・スリッページはライブ設定と一致。
  • ウォークフォワード(rolling 90→30)でパラメタの安定領域を探す(ピークではなく台地を採用)。

13) 期待値の形(Rベース)を把握

  • トレードごとの R = pnl / (risk_per_trade * equity_at_entry) を配列化。
  • 平均R / 標準偏差 / 左尾 を可視化 → リスクリミット(-5R/-3R)や部分利確の利点が見える。

セキュリティ・SRE(安全性)

14) secrets運用・NTP・fsync

  • .bitbank_secrets は600、\r除去、キー更新手順をREADMEに。
  • NTP同期を定期確認(nonce/キャンドル境界のズレ防止)。
  • save_state()fsync オプション(落雷・再起動時の巻き戻り防止)。

すぐ貼れるミニパッチ(抜粋)

トレーリングの切り上げ保持(ヒットなしでも)

// EXIT分岐の else 側
} else {
    let mut st = state.lock().await;
    if st.position > 0.0 {
        st.stop_price = Some(new_sp);
    }
}

PnL(両側手数料込み・前の数量で記録)

let buy_px = entry_price.unwrap_or(exit_px);
let fee = costs_pct(&cfg);
let pnl = ((exit_px * (1.0 - fee)) - (buy_px * (1.0 + fee))) * position;
// SELL_LIVE ログは position クリア前の数量で
let _ = append_trade_csv(..., position, pnl);

next_nonce()(衝突ゼロ版)※api_utils側

static LAST_NONCE: AtomicU64 = AtomicU64::new(0);
pub fn next_nonce() -> String {
    let now = Utc::now().timestamp_millis() as u64;
    let mut prev = LAST_NONCE.load(Ordering::Relaxed);
    loop {
        let next = prev.max(now) + 1;
        match LAST_NONCE.compare_exchange(prev, next, Ordering::SeqCst, Ordering::SeqCst) {
            Ok(_) => return next.to_string(),
            Err(cur) => prev = cur,
        }
    }
}

優先順位(実装順)

  1. P0:ボラ/スプレッド/時間帯フィルター、トレーリング保持、IOC→Market、滑りガード
  2. P1:部分利確、MTF整合、軽量パラ探索
  3. P2:Private WS、スプレッド自覚、レジーム、停止モード
  4. 可観測:/metrics、アラート
  5. 検証:同一ロジックBT+WF、R分布の可視化

-Bot, 開発ログ