Bot

開発記録#192(2025/4/22)戦略ログ「Premium‑Guard DeltaGuard DCA」

2025年4月22日

ご利用にあたっての重要な注意事項(必読)

This code is provided as-is for educational purposes only.
It does not constitute investment advice. Use entirely at your own risk.


1. 本コードの性質

  • 実売買を実行します
    • 現物DCA購入、先物ショートヘッジ、ポジション全解除などを自動で発注します。
  • config.yaml に API キー/パラメータを記述すると、Bybit 本番環境でリアルトレードが走ります。
  • 誤設定・過大なポジションサイズにより 重大な損失 が生じる可能性があります。

2. 安全な導入手順 – “必ずTestnetから”

手順やること理由
① Testnetキー発行Bybit Testnet の API キーを取得リアル資金を守る
config.yaml にセットhedge_qtycapital_percent を極小値で設定想定外の建玉を防ぐ
③ 24時間連続稼働テストDCA→ヘッジ→解除→損切りの全フローを確認ロジック漏れを洗い出す
④ 本番キー投入本番資金は余剰資金のみリスク許容度に合わせる

⚠️ API キーをブログ・SNS・GitHub などに絶対公開しないでください。

3. APIキーと設定ファイルの取り扱い

  • 本記事の config.yaml完全なダミー です。実キーは必ずローカル .env など非公開領域で管理してください。
  • Git 管理時は .gitignoreconfig.yaml / .env を除外し、誤 push 事故 を防止してください。

4. Prometheus メトリクスの公開範囲

  • 本コードは start_http_server()ポートを外部に開放 します。
    • 本番運用時は ファイアウォール / IP 制限 / Basic 認証 等を必ず設定してください。
    • ダッシュボードをインターネット公開する場合は 逆プロキシ経由(nginx 等) でアクセス制御を推奨。

5. 免責事項(Disclaimer)

  • 本コードは 学習素材 として無償提供されています。
  • 当ブログおよび筆者は、
    1. 本コードの利用・改変・再配布、
    2. それらに伴う金銭的損失・機会損失・第三者への損害
      について 一切の責任を負いません
  • Not financial advice. 投資判断はすべてご自身で行ってください。

6. 推奨される使い方

  1. コードの構造とリスク管理ロジックを読み解く
  2. Testnet で挙動を観察・デバッグ
  3. 自分の戦略/資金規模に合わせてパラメータをチューニング
  4. 本番環境では 設定の再確認・少額資金から段階的に 運用

Premium‑Guard DeltaGuard Dca

参考記事:開発記録#190(2025/4/21)戦略抽出「デルタガードDCA戦略&深掘り」

import asyncio
import logging
import yaml
import time
from datetime import datetime, timedelta
import aiohttp
import pybotters
from prometheus_client import Gauge, start_http_server
import schedule

# ロギングを設定
tool_logger = logging.getLogger('PremiumGuard')
logging.basicConfig(level=logging.INFO)

# 設定ファイルを読み込む
with open('config.yaml', 'r') as f:
    CONFIG = yaml.safe_load(f)

API_KEYS = CONFIG['api_keys']
CAPITAL_PERCENT = CONFIG.get('capital_percent', 0.01)
TWAP_PERIODS = CONFIG.get('twap_periods', 2)
P_THRESHOLD = CONFIG.get('p_threshold', 0.00005)
OI_LONG_HIGH = CONFIG.get('oi_long_high', 0.6)
OI_LONG_LOW = CONFIG.get('oi_long_low', 0.4)
SL_CUT = CONFIG.get('stop_loss_pct', 0.15)
TRAILING_PCT = CONFIG.get('trailing_pct', 0.20)
DELTA_52W = CONFIG.get('delta_52w', 0.05)
JST_OFFSET = CONFIG.get('jst_offset_hours', 9)

# Prometheus用のメトリクス定義

gauge_p = Gauge('premium_index', 'プレミアム指数 P')
gauge_fr = Gauge('funding_rate', '資金調達率 FR')
gauge_oi = Gauge('oi_long_ratio', '建玉のロング比率')
gauge_pnl = Gauge('current_pnl_pct', '現在の損益率')

class DataCollector:
    def __init__(self, client):
        self.client = client
        self.history = []  # (timestamp, P, oi_long_ratio) の履歴

    async def fetch_prices_and_oi(self):
        # 指数価格を取得
        idx = await self.client.get('/v5/market/index-price', params={'symbol': CONFIG['symbol']})
        idx_data = await idx.json()
        index_price = float(idx_data['result'][0]['indexPrice'])

        # 永久先物のマーク価格を取得
        fut = await self.client.get('/v5/market/tickers', params={'symbol': CONFIG['symbol']})
        fut_data = await fut.json()
        mark_price = float(fut_data['result'][0]['markPrice'])

        # プレミアム指数 P を計算
        p = mark_price / index_price - 1

        # ロング/ショート比率を取得
        lr = await self.client.get('/v5/market/long-short-ratio', params={
            'symbol': CONFIG['symbol'], 'interval': '1h', 'limit': 1
        })
        lr_data = await lr.json()
        oi_long_ratio = float(lr_data['result'][0]['longShortRatio']) / 100.0

        timestamp = datetime.utcnow()
        self.history.append((timestamp, p, oi_long_ratio))
        # 履歴を TWAP_PERIODS 件に制限
        if len(self.history) > TWAP_PERIODS:
            self.history.pop(0)

        gauge_p.set(p)
        gauge_oi.set(oi_long_ratio)

        return p, oi_long_ratio

class SignalEvaluator:
    def __init__(self):
        self.hedge_counter = 0
        self.normal_counter = 0
        self.last_peak_price = None

    def evaluate(self, p, oi_long_ratio, interest_rate):
        # 資金調達率 FR を計算
        fr = interest_rate + p
        gauge_fr.set(fr)

        # 予備ヘッジシグナル
        preempt = (p > P_THRESHOLD and oi_long_ratio > OI_LONG_HIGH)
        # 通常ヘッジシグナル
        normal = (fr > 0)
        # クイック解除シグナル
        unwind = (p < -P_THRESHOLD or oi_long_ratio < OI_LONG_LOW)

        if preempt:
            self.hedge_counter += 1
        else:
            self.hedge_counter = 0

        if normal:
            self.normal_counter += 1
        else:
            self.normal_counter = 0

        if unwind:
            return 'unwind'
        if self.hedge_counter >= TWAP_PERIODS:
            return 'preemptive_hedge'
        if self.normal_counter >= TWAP_PERIODS:
            return 'normal_hedge'
        return None

class TradeManager:
    def __init__(self, client):
        self.client = client
        self.disable_until = None

    async def dca_buy(self):
        # 現物買い量を計算(USDT残高×CAPITAL_PERCENT)
        balance = await self.client.get('/v5/account/balance', params={'coin': 'USDT'})
        bal_data = await balance.json()
        usdt_bal = float([x for x in bal_data['result'] if x['coin'] == 'USDT'][0]['availableBalance'])
        alloc = usdt_bal * CAPITAL_PERCENT
        price = await self.client.get('/v5/market/tickers', params={'symbol': CONFIG['symbol']})
        price_data = await price.json()
        spot_price = float(price_data['result'][0]['lastPrice'])
        qty = alloc / spot_price

        await self.client.post('/v5/order/create', data={
            'category': 'spot', 'symbol': CONFIG['symbol'], 'side': 'Buy', 'orderType': 'Market',
            'qty': str(qty)
        })
        tool_logger.info(f'DCA買い注文を発注しました: qty={qty}')
        return qty

    async def hedge_short(self):
        # 先物ショート建玉をマーケットで実行
        await self.client.post('/v5/order/create', data={
            'category': 'linear', 'symbol': CONFIG['symbol'], 'side': 'Sell', 'orderType': 'Market',
            'qty': CONFIG['hedge_qty']
        })
        tool_logger.info('ヘッジショート建玉を開始しました')

    async def unwind(self):
        # 全先物ポジションを決済
        await self.client.post('/v5/order/cancel-all', data={'symbol': CONFIG['symbol'], 'category': 'linear'})
        tool_logger.info('ヘッジポジションを解除しました')

    async def close_all(self):
        # 全ポジション(現物・先物)を全てクローズ
        await self.client.post('/v5/order/cancel-all', data={'symbol': CONFIG['symbol'], 'category': 'linear'})
        await self.client.post('/v5/order/cancel-all', data={'symbol': CONFIG['symbol'], 'category': 'spot'})
        tool_logger.info('全ポジションをクローズしました')

class PnLMonitor:
    def __init__(self, client, tm):
        self.client = client
        self.tm = tm
        self.peak_price = 0

    async def check(self):
        # P&L監視(ダミー算出)
        pnl_pct = 0.0
        gauge_pnl.set(pnl_pct)

        # 損切り(DD15%超)
        if pnl_pct < -SL_CUT:
            await self.tm.close_all()
            self.tm.disable_until = datetime.utcnow() + timedelta(hours=24)
            tool_logger.warning('損切り条件を満たしました。24時間停止します')

        # トレーリング利確(最高値からTRAILING_PCT下落)
        ticker = await self.client.get('/v5/market/tickers', params={'symbol': CONFIG['symbol']})
        last_price = float((await ticker.json())['result'][0]['lastPrice'])
        self.peak_price = max(self.peak_price, last_price)
        if last_price < self.peak_price * (1 - TRAILING_PCT):
            await self.tm.close_all()
            tool_logger.info('トレーリング利確を実行しました')

async def scheduler_loop(client, dc, se, tm):
    # 毎日JST09:00にDCA買いをスケジュール
    def job():
        asyncio.create_task(tm.dca_buy())
    schedule.every().day.at(CONFIG['dca_time']).do(job)

    while True:
        schedule.run_pending()
        await asyncio.sleep(1)

async def main():
    # Prometheusメトリクス用HTTPサーバーを起動
    start_http_server(CONFIG.get('metrics_port', 8000))

    async with pybotters.Client(api_key=API_KEYS['key'], api_secret=API_KEYS['secret']) as client:
        dc = DataCollector(client)
        se = SignalEvaluator()
        tm = TradeManager(client)
        pnl = PnLMonitor(client, tm)

        # DCAエントリーのスケジュール開始
        asyncio.create_task(scheduler_loop(client, dc, se, tm))

        while True:
            # 停止期間中はスキップ
            if tm.disable_until and datetime.utcnow() < tm.disable_until:
                await asyncio.sleep(60)
                continue

            # データ収集→シグナル判定→注文実行
            p, oi = await dc.fetch_prices_and_oi()
            interest_rate = CONFIG.get('interest_rate', 0.0003)
            sig = se.evaluate(p, oi, interest_rate)
            if sig in ['preemptive_hedge', 'normal_hedge']:
                await tm.hedge_short()
            elif sig == 'unwind':
                await tm.unwind()

            # P&L監視
            await pnl.check()

            await asyncio.sleep(CONFIG.get('poll_interval', 60))

if __name__ == '__main__':
    asyncio.run(main())
  • DataCollector:現物・先物価格とロング/ショート比率を取得し、プレミアム指数の TWAP を計算
  • SignalEvaluator:プレミアム指数(P)、建玉比率(OI)、資金調達率(FR)に応じて「予備ヘッジ」「通常ヘッジ」「クイック解除」のシグナルを判定
  • TradeManager:定額 DCA 買いの発注、先物ショート建玉、建玉解除、全ポジションクローズを実行
  • PnLMonitor:15% のドローダウンで損切り、最高値からの 20% 下落でトレーリング利確を監視・実行
  • Schedulerschedule ライブラリで毎営業日09:00 JST に定額買いを自動実行
  • Metrics:Prometheus を介して P, FR, OI, P&L を収集し、Grafana で可視化可能

あとは config.yaml に API キーやしきい値を設定し、スクリプトを起動するだけで、常時ポーリング → シグナル判定 → 注文実行 → メトリクス公開が自動で動作する。

config.yaml の例

# config.yaml (例・公開しても安全なモック)
api_keys:
  key: "YOUR_API_KEY"
  secret: "YOUR_API_SECRET"

symbol: "BTCUSDT"
hedge_qty: "0.01"
capital_percent: 0.01
p_threshold: 0.00005
oi_long_high: 0.6
oi_long_low: 0.4
stop_loss_pct: 0.15
trailing_pct: 0.2
dca_time: "09:00"
interest_rate: 0.0003
poll_interval: 60
metrics_port: 8000

1. 準備:設定とライブラリの読み込み

  1. 設定ファイル(config.yaml)を読み込む
    ‑ APIキー、取引ペア、各種しきい値(閾値)などを外部ファイルから読み込む。
    ‑ こうすることで、値を変更したいときにコードを触らずに済むようになる。
  2. 必要なライブラリのインポート
    pybotters:Bybit などの取引所 API へ非同期リクエストを送るため
    schedule:毎日決まった時刻に処理を実行するため
    prometheus_client:メトリクス(P, FR, OI, P&L)を収集し、Grafana で可視化できるようにするため
    ‑ その他:日時操作、ログ出力、非同期処理用の標準モジュールなど

2. データ収集モジュール:DataCollector

  1. 価格・建玉情報の取得
    • 現物(スポット)のMark Price
    • 永久先物のMark Price
    • 直近1時間のロング/ショート比率
  2. プレミアム指数 P の計算  P = (先物マーク価格 ÷ 指数価格) − 1
  3. TWAP(時間加重平均)対応の履歴管理
    ‑ 直近2サイクル分の P, OI をリストに保存し、平均化の準備をする。
  4. Prometheus メトリクスへの登録
    ‑ 取得した P と OI をすぐにダッシュボードで見えるように送信します。

3. シグナル判定モジュール:SignalEvaluator

  1. 基本資金調達率(FR)の計算 FR = 固定金利 I + P
  2. 3種類のシグナル判定
    • 予備ヘッジ
      P がしきい値以上 & OI_LongRatio がしきい値以上 → 先物ショート建玉を早めにスタート
    • 通常ヘッジ
      FR > 0 → P を加味した結果、持ち越しコストが発生すると判断
    • クイック解除
      P が負しきい値以下 または OI_LongRatio が低すぎる → すぐにヘッジ解除
  3. 連続シグナル判定
    ‑ 「2サイクル連続で同じ判定が出たらアクション」という仕組みで、ノイズ(一時的な揺れ)を除去します。

4. 注文実行モジュール:TradeManager

  1. 定額DCA買い (dca_buy)
    • USDT 残高の 1%(例)を使ってマーケット成行買い
    • 毎朝09:00に自動で呼び出される
  2. 先物ショート建玉 (hedge_short)
    • シグナルに応じて先物を成行ショートで建てる
  3. ヘッジ解除 (unwind)
    • 先物ポジションを全てキャンセルしてクローズ
  4. 全ポジション一括クローズ (close_all)
    • 損切りや利確で現物・先物ともに即座にクローズ

5. 損益監視モジュール:PnLMonitor

  1. ドローダウン(DD15%)の判定
    ‑ 含み損が資産の15%以上になったら close_all() を呼び出し、
    ‑ さらに24時間スクリプト実行を停止してリスクを抑制
  2. トレーリング利確(ピーク比20%下落)
    ‑ 保有中の最高価格を更新し続け、
    ‑ 現在価格が最高値の80%を下回ったら利確

6. 定期実行(Scheduler)

  • schedule ライブラリで「毎日09:00 JSTに dca_buy() を実行」と予約
  • メインループとは別スレッド的に動かし、
  • 毎秒 schedule.run_pending() を呼んでキューに溜まったジョブを実行します。

7. メインループ:main()

  1. Prometheus サーバ起動
    ‑ メトリクスを 8000 番ポートで公開
  2. APIクライアント生成
    pybotters.Client に APIキーを渡して接続
  3. 無限ループでの処理(60秒ごと)
    1. 停止期間中 かどうかをチェック
    2. データ収集fetch_prices_and_oi()
    3. シグナル判定evaluate()
    4. 注文実行(予備ヘッジ/通常ヘッジ/解除)
    5. 損益監視check()

まとめ

  • 設定(APIキー・しきい値)を外部化
  • 非同期×スケジューラで高い応答性
  • Prometheusで「P・FR・OI・PnL」を可視化
  • 3層のヘッジ判定DCA出口ルールでリスク管理を重ねる

開発フロー概要

flowchart TD
  A[要件定義] --> B[環境構築]
  B --> C[モジュール設計]
  C --> D[データ収集実装]
  D --> E[シグナル判定実装]
  E --> F[注文実行実装]
  F --> G[P&L監視実装]
  G --> H[スケジューラ実装]
  H --> I[メトリクス統合]
  I --> J[単体テスト & モック検証]
  J --> K[結合テスト(テストネット)]
  K --> L[通知機能(Slack/Email)実装]
  L --> M[プロダクションデプロイ]
  M --> N[モニタリング & アラート設定]
  N --> O[運用・改善サイクル]

フェーズ 1: 要件定義

  1. 戦略仕様の確定
    • Premium Index, OI, FR シグナルの閾値
    • DCA 資金割合、ストップロス/利確ルール
    • 通知要件(Slack/Email)
    • 可視化要件(Grafana メトリクス項目)
  2. スコープ設定
    • まずは P/OI 収集+FR並行テストのみ → 正常動作確認
    • 段階的に DCA → 注文 → P&L監視 → 通知 → デプロイへ

フェーズ 2: 環境構築

  1. 開発環境
    • Python ≥3.12(pyenv, Poetry で仮想環境)
    • VSCode + Python 拡張
  2. 依存ライブラリ
    • pybotters, aiohttp(非同期 API)
    • schedule(定時実行)
    • prometheus_client(メトリクス公開)
    • loguru または標準 logging
    • pyyaml(設定ファイル)
  3. 設定管理
    • config.yaml に APIキー/シンボル/各種閾値を集約

フェーズ 3: モジュール設計

モジュール名役割
DataCollector価格・OI 取得、TWAP 計算
SignalEvaluatorP/OI/FR によるヘッジ・解除の判定
TradeManagerDCA 買い、先物ショート、全クローズ
PnLMonitorDD15%・トレーリング利確の監視
Scheduler毎日 DCA 実行スケジュール
MetricsExporterPrometheus へ P, FR, OI, PnL を公開
NotifierSlack/Email 通知(拡張フェーズ)

フェーズ 4: 各モジュールの実装

  1. DataCollector
    • Bybit API へ非同期リクエスト
    • プレミアム指数計算・履歴管理
  2. SignalEvaluator
    • 連続判定用カウンタ実装
    • 判定結果を文字列('preemptive_hedge' など)で返却
  3. TradeManager
    • DCA 買い(現物マーケット成行)
    • 先物ショート/解除 API 呼び出し
    • 全クローズロジック
  4. PnLMonitor
    • 含み損率計算(または簡易算出)
    • DD 超過時・トレーリング時の close_all()
  5. Scheduler
    • schedule.every().day.at('09:00').do(dca_buy)
  6. MetricsExporter
    • Gauge 定義 → 各モジュールから set()
  7. Notifier(後工程)
    • slack-sdk などを使いアクション毎に通知

フェーズ 5: テスト

  1. 単体テスト & モック
    • HTTP クライアントをモックして DataCollector を検証
    • SignalEvaluator の閾値判定テスト
  2. 結合テスト
    • テストネット環境に接続し、一連の流れをシミュレーション
    • DCA → ヘッジ → 解除 → 損切り/利確の動作確認

フェーズ 6: デプロイ & 運用

  1. デプロイ
    • Docker イメージ化 → Kubernetes / VM へ配備
    • 環境変数 & シークレット管理
  2. モニタリング
    • Prometheus + Grafana ダッシュボード構築
    • アラートルール(DD 超過、API エラー多発時など)
  3. 運用サイクル
    • 定期的な戦略バックテスト結果レビュー
    • パラメータ調整・閾値最適化
    • 通知ログの分析 → フロー改善

おまけ:Delta Guard Dca Prototype

# -*- coding: utf-8 -*-
import asyncio
import yaml
import schedule
import time
from datetime import datetime, timedelta
import pybotters
import logging

# --- 初期設定 ---
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s')

# config.yaml から設定を読み込む
# 必要な項目:
# api_key, api_secret, symbol, capital_percent, dca_time, hedge_interval_hours, poll_interval_sec, stop_loss_pct
with open('config.yaml', 'r') as f:
    CONFIG = yaml.safe_load(f)

API_KEY = CONFIG['api_key']
API_SECRET = CONFIG['api_secret']
SYMBOL = CONFIG.get('symbol', 'BTCUSDT')
CAPITAL_PERCENT = CONFIG.get('capital_percent', 0.01)
DCA_TIME = CONFIG.get('dca_time', '09:00')           # JST の定額買い時刻
HEDGE_INTERVAL = CONFIG.get('hedge_interval_hours', 1)  # ヘッジ判定の間隔(時間)
POLL_INTERVAL = CONFIG.get('poll_interval_sec', 60)     # 損切り監視の間隔(秒)
STOP_LOSS_PCT = CONFIG.get('stop_loss_pct', 0.15)       # ドローダウン許容率(15%)
DISABLE_DURATION = timedelta(hours=24)                  # 損切り後の停止期間

class DeltaGuardDCA:
    def __init__(self):
        # Bot の状態管理
        self.disable_until = None   # 停止期間終了時刻
        self.capital_base = None    # 最初の資本を保持
        self.spot_amt = 0.0         # 累積購入 BTC 枚数

    async def dca_buy(self, client: pybotters.Client):
        """
        定額DCA買い:USDT残高のCAPITAL_PERCENT分を現物BTC成行買い
        """
        # 停止期間中は実行しない
        if self.disable_until and datetime.utcnow() < self.disable_until:
            logging.info('DCA買い:停止期間中のためスキップ')
            return

        # USDT 残高取得
        resp = await client.get('/v5/account/balance', params={'coin': 'USDT'})
        data = await resp.json()
        usdt_bal = float([x for x in data['result'] if x['coin']=='USDT'][0]['availableBalance'])

        # 買付額と枚数計算
        alloc = usdt_bal * CAPITAL_PERCENT
        resp2 = await client.get('/v5/market/tickers', params={'symbol': SYMBOL})
        price = float((await resp2.json())['result'][0]['lastPrice'])
        qty = alloc / price

        # 成行買い注文
        await client.post('/v5/order/create', data={
            'category':'spot', 'symbol':SYMBOL, 'side':'Buy', 'orderType':'Market', 'qty':str(qty)
        })
        logging.info(f'DCA買い executed: qty={qty:.6f} BTC at price={price:.1f} USDT')

        # 初回のみ資本ベースを記録
        if self.capital_base is None:
            self.capital_base = usdt_bal
        self.spot_amt += qty

    async def hedge(self, client: pybotters.Client):
        """
        FR ヘッジ:FR>0で先物ショート建玉、FR<=0でクローズ
        """
        # 停止期間中は実行しない
        if self.disable_until and datetime.utcnow() < self.disable_until:
            logging.info('ヘッジ処理:停止期間中のためスキップ')
            return

        # 資金調達率取得
        resp = await client.get('/v5/public/funding/prev-funding-rate', params={'symbol':SYMBOL})
        fr = float((await resp.json())['result'][0]['fundingRate'])

        # 現物評価額 = 今のBTC価格 * 保有枚数
        resp2 = await client.get('/v5/market/tickers', params={'symbol': SYMBOL})
        price = float((await resp2.json())['result'][0]['lastPrice'])
        notional = price * self.spot_amt

        # ヘッジ枚数計算(先物ショート枚数 = 現物評価額 ÷ 先物価格)
        hedge_qty = notional / price

        if fr > 0:
            # ヘッジ建玉
            await client.post('/v5/order/create', data={
                'category':'linear', 'symbol':SYMBOL, 'side':'Sell', 'orderType':'Market', 'qty':str(hedge_qty)
            })
            logging.info(f'ヘッジON: FR={fr:.6f} > 0, qty={hedge_qty:.6f}')
        else:
            # ヘッジ解除
            await client.post('/v5/order/cancel-all', data={'symbol':SYMBOL, 'category':'linear'})
            logging.info(f'ヘッジOFF: FR={fr:.6f} <= 0, closed all')

    async def check_stop_loss(self, client: pybotters.Client):
        """
        損切り監視:含み損がSTOP_LOSS_PCT超で全クローズ+停止期間セット
        """
        if self.capital_base is None:
            return
        # 現在の評価額取得(簡易:USDT残高+保有BTC評価)
        resp = await client.get('/v5/account/balance', params={'coin':'USDT'})
        usdt_bal = float([x for x in (await resp.json())['result'] if x['coin']=='USDT'][0]['availableBalance'])
        resp2 = await client.get('/v5/market/tickers', params={'symbol': SYMBOL})
        price = float((await resp2.json())['result'][0]['lastPrice'])
        total_val = usdt_bal + price * self.spot_amt

        # ドローダウン率計算
        dd = 1 - (total_val / self.capital_base)
        if dd > STOP_LOSS_PCT:
            # 全ポジション決済
            await client.post('/v5/order/cancel-all', data={'symbol':SYMBOL, 'category':'linear'})
            await client.post('/v5/order/cancel-all', data={'symbol':SYMBOL, 'category':'spot'})
            self.disable_until = datetime.utcnow() + DISABLE_DURATION
            logging.warning(f'損切り発動: DD={dd:.2%} > {STOP_LOSS_PCT:.2%}, 停止 until {self.disable_until}')

    def run(self):
        """
        スケジュール設定とメインループ
        """
        # schedule で定期ジョブを登録
        schedule.every().day.at(DCA_TIME).do(lambda: asyncio.create_task(self.dca_buy(client)))
        schedule.every(HEDGE_INTERVAL).hours.do(lambda: asyncio.create_task(self.hedge(client)))

        loop = asyncio.get_event_loop()

        # 損切り監視ループ
        async def pnl_loop():
            while True:
                await self.check_stop_loss(client)
                await asyncio.sleep(POLL_INTERVAL)

        # pybotters クライアントで接続
        async def main():
            nonlocal client
            async with pybotters.Client(api_key=API_KEY, api_secret=API_SECRET) as client:
                # 損切り監視タスク開始
                asyncio.create_task(pnl_loop())
                # schedule を動かす
                while True:
                    schedule.run_pending()
                    await asyncio.sleep(1)

        loop.run_until_complete(main())

if __name__ == '__main__':
    bot = DeltaGuardDCA()
    bot.run()

プロトタイプとして、以下の機能を日本語コメント付きで実装しました。

  1. 定額DCA買い(現物BTC、資本の1%ずつ成行)
  2. Funding Rate ヘッジ(FR>0で先物ショート、FR≤0で解除)
  3. ドローダウン損切り(含み損15%以上で全ポジクローズ+24h停止)
  4. スケジューリング(DCA:毎日09:00 JST、ヘッジ:1時間ごと、損切り監視:60秒ごと)

config.yaml に必要設定を入れて、python delta_guard_dca_prototype.py で実行。

-Bot