今日は実弾で回し始めたスイング系botのみ開発ログを掲載します。現在、実弾で回しながら追加で必要な機能を積んでいる段階です。
MMbotなどもいじっていましたが、特に新しいことはしていないので開発ログは割愛します。
起動、停止、ログ監視のmakeコマンド実装まで終わった。これでしばらく放置できる。次のbot作る。 https://t.co/a0W5zDVD9P
— よだか(夜鷹/yodaka) (@yodakablog) August 26, 2025
スイング系bot開発ログ
タイムライン
0) キックオフ(Python → Rust 移行)
- 目的:国内CEX(bitbank) の現物スイングBotを実弾運用できる形で構築。
- 初期版:Python+ccxtで雛形 → Rust単一バイナリに移植(堅牢性・速度・運用性優先)。
1) データ取得まわりの安定化
- candlestickの10000エラー発生:URL粒度の誤りが原因。
- 修正:
pair=btc_jpy
(アンダースコア)、1hour=YYYYMMDD
、4hour/1day=YYYY
で完全準拠。
- 修正:
- 取得戦略刷新:今日→過去へのバックワード走査、MIN_BARS 到達で即停止。
- パースの堅牢化:
- 文字列/数値両対応、
len<6
/invalid ts
/vol=0
安全スキップ、type
はeq_ignore_ascii_case("1hour")
。
- 文字列/数値両対応、
- 取得失敗時はフォワード走査にフォールバック、RateLimiter&指数バックオフ実装。
2) Private API 認証(20001)切り分け → 解決
- 最小疎通テスト
/v1/user/assets
をHTTP200 & success==1で合格判定。 - 署名仕様をユーティリティに集約:
- GET:
nonce + path (+ "?" + query)
- POST:
nonce + body_json(送るJSON文字列そのもの)
- HMAC-SHA256 → Base64(hex は廃止)。
- GET:
next_nonce()
を絶対単調増加に(ms衝突をAtomicU64で回避)。.bitbank_secrets
の厳密トリム(前後空白/改行/\r
除去)。- 起動時のプリフライトを1本に絞り(
/assets
のみ)、nonce連打による誤検知を回避。
3) 売買ロジック・実弾フロー実装
- Donchian×ATR(4h/1h相当)
- ENTRY:上抜け(
price >= hi
)、リスク基準サイズ - EXIT:トレーリングストップ(
stop=max(stop, lo)
)、Marketクローズ
- ENTRY:上抜け(
- 手数料の扱い
- 注文価格には手数料を乗せない(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
しないパターンへ全面移行:state
から必要情報をスナップショット- ロック解放→HTTP実行
- 再ロックして結果を反映
- 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
(履歴)→unknown
(position無変更) - 取消: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)
- プリフライト
make build
→make run
- 起動時ログで
/v1/user/assets
が success==1 を確認(1回だけ実行) - Public candlestick で直近バーが取得できていることを確認
- 最小カナリア(0.1R)
- BUY:Market もしくは IOC攻撃的指値
- 約定確認:REST 2–3回(合計≤10s)。のち WS に移行
- SELL:Market でクローズ
[SUM]
と[EXIT]/[SELL_LIVE]/[SELL_FAIL]
を確認
- 段階昇格
- 0.1R → 0.5R → 1.0R(Hard停止ONのまま)
- ボラ/スプレッドが極端に悪い時間帯は IOC で滑り抑制
- 停止/再開
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 Exit(
stop = 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)
qty
の 50% を +1R で利確、残りをトレーリング。
let tp = entry + 1.0 * cfg.stop_mult * atr_entry; // 1R
- 実装:
state.partial_taken: bool
とtp_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 を常時保持。
- ENTRY:
ask + tick * n
でIOC、n
はspread/avg_spread
の関数に。 - SELL:Market優先だが、板厚が十分なら
bid - tick * n
IOC → 未約定ならMarket。
9) 風向き判定(レジーム)と“取引停止モード”
- ADX(14) or ReturnのHurst/チャープで トレンド相/レンジ相 をざっくり分類。
レンジ相はDonchianのブレイクを抑制(ボラ・スプレッド比が悪い時)。 - 自動停止モード:
連続の SELL_FAIL ≥ N、20001/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, } } }
優先順位(実装順)
- P0:ボラ/スプレッド/時間帯フィルター、トレーリング保持、IOC→Market、滑りガード
- P1:部分利確、MTF整合、軽量パラ探索
- P2:Private WS、スプレッド自覚、レジーム、停止モード
- 可観測:/metrics、アラート
- 検証:同一ロジックBT+WF、R分布の可視化
公式はBase64準拠だけどhexでたまたま通ったように見えることもあるのか。よく分からん。とりあえず公式に寄せておいたけど、モヤっとする。 https://t.co/ILryLelxZZ
— よだか(夜鷹/yodaka) (@yodakablog) August 26, 2025