2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

構造化意思決定AI「Jev」を暗号資産トレードへ組み込む: シャドー運用と評価設計の実践事例

2
Last updated at Posted at 2026-09-25

この記事は技術的な実装・検証方法を扱います。投資助言ではありません。金融商品の価格予測はできず、実運用には損失、流動性、障害、取引所リスクが伴います。

TL;DR

この記事は「仮想通貨取引所から入手できる対象銘柄の情報をJevへ入力し、売買のジャッジをJevにしてもらう」という、シンプルな発想から書いたものです。記事は生成AIに書かせたものを元に修正しています。ナマモノである市場には静的なルールベースロジックより、AIで動的にジャッジさせた方が良い結果になるのでは?という期待が動機です。

はじめに

LLMなどのAIモデルに相場データを見せてBUY、SELL、HOLDを選ばせる仕組み自体は、プロンプトを組めば容易に実装できます。
しかし実運用において難しいのは、AIの出力を安易に実発注へ結び付けず、既存システムを壊さずに「再現可能な比較実験」として組み込むことです。

本記事では、状態と選択肢から構造化判定を返すAI推論基盤「Jev」(TypeSafe AI SystemOne API)の活用事例として、既存のルールベース自動売買システムと並走させているシャドー運用のアーキテクチャと評価設計を紹介します。対象は暗号資産の現物取引ですが、状態・選択肢・シャドー台帳・発注分離という設計原則は株式やFXにも応用できます。一方、発注、リスク管理、取引時間、ブローカーAPIとの統合は市場ごとに作り直す必要があります。

目標

システム構築における目標は次の4点です。

  1. 市場データと保有状況をJevへ渡し、構造化された売買判断を受け取る
  2. AI側の障害や遅延が既存の実取引を停止させず、影響を限定するフェイルセーフを確立する
  3. AI専用の仮想台帳を並走させ、既存ロジックと同一条件でフォワードテストを行う
  4. AIの判断と、その後の価格変化・仮想台帳の推移を後から厳密に検証できるようにする

特に重要なのは「同一条件での成績比較」です。AIが妥当に見える判断を下しても、取引手数料、スプレッド、最小注文サイズ、クールダウンといった現実の制約を差し引いた資産曲線で既存ルールを上回れるかは全く別の問題です。

全体構成

取引所 API
  |  ticker / order book / closed candles / balances
  v
トレードプロセス
  |- 既存のルールベース判定 ----> 実発注(従来経路)
  |
  `- Jev Shadow 判定 ----------> Jev API
       |                            |
       |<----- structured JSON -----+
       |
       `- Jev 仮想台帳(今回: 2トラック) -> JSONL / NASログ

Jevは注文を実行しません。発注権限を持つのはトレードプロセスのみです。Jevのタイムアウト、HTTPエラー、JSONスキーマ不正、未定義の選択肢などはすべて「AI判定失敗」として記録・棄却し、既存ロジックによる実取引を継続します。

この構成の目的は、十分な期間シャドー台帳でルールベース判定との比較を行い、手数料込みの資産推移、最大ドローダウン、API障害率を確認することです。将来的に実発注ロジックをJev判定へ置き換える場合であっても、残高確認、注文サイズ制限、頻度制限、緊急停止といったガードレールはすべてトレードプロセス側に残します。

執筆時点(2026-09-25)のログでは、114回のJev判定で選択肢外・型不正の応答は0件でした。一方で、入力スナップショット構築失敗が2件、APIタイムアウトが1件あり、いずれも既存ロジックを継続するフォールバックで処理されています。Jevが実取引へ影響してリスク制約を逸脱した事例はありません。もちろん、急変動や長期レンジなど多様な相場サイクルを網羅するにはまだ観測期間が必要です。現段階では優劣を早急に断定せず、フェイルセーフとログ収集が機能していることを確認する段階です。

実測したコストとレイテンシ

Jev APIを利用した直近のリクエスト実績では、入力がおよそ11,500トークン、出力は約200トークンでした。15分間隔で実行した場合、1か月(30日換算)で2,880リクエスト、月間の入力トークンは約3,312万トークンになります。

Jevの公開料金は入力100万トークンあたり0.042米ドル、出力は課金対象外です。Jevの製品情報を確認した2026-09-25時点で、1米ドル=約158.3円として換算すると、入力100万トークンあたり約6.65円、月額コストの概算は約220円です。為替と料金体系の変更により、この金額は変動します。

11,500 tokens/request x 2,880 requests/month = 33.12 million tokens/month
33.12 x 6.65 JPY = about 220 JPY/month

同条件で測定したJev APIのHTTP往復レイテンシはおおむね0.7〜0.8秒でした。呼び出しは同期HTTPのため、各判定サイクルは応答を待ちます。ネットワークや入力サイズによるブレを考慮し、クライアント側では3秒で打ち切るタイムアウトを設定しています。タイムアウト時は、最大3秒の遅延後にJev判定を棄却し、既存ロジックを継続します。

このシステムで採用した二つのトラック

このシステムでは、長期保有と短期売買を混在させないため、現物残高を用途の異なる二つの内部台帳として管理しています。これはJev連携に必須の制約ではなく、運用の都合で採用している資産配分戦略です。

  • 長期トラック (long): 長期保有を目的にした現物配分
  • 短期トラック (short): スイングトレード等の売買機会を狙う現物配分

ここでのshortは空売り(ショート)を意味しません。借入や証拠金取引を使わず、法定通貨で現物を買い、保有した現物だけを売却する短期用の枠です。以後、本文では混同を避けるため「長期トラック」「短期トラック」と呼びます。

取引所の実残高は一つですが、内部的に対象資産と法定通貨をトラック別に管理することで、短期トラックの売却処理が長期保有分の現物を誤って巻き込む事故を防ぎます。単一戦略のシステムであれば台帳は一つで構いません。

入力データの設計

価格単体ではAIも有意な判断ができません。今回は以下を1リクエストのstateに含めています。

区分 主なデータ
市場 現在値、24時間変化率、高値・安値、出来高、最良気配、スプレッド、板の偏り
時系列 15分、1時間、4時間、日足の確定ローソク足
指標 EMA、RSI、ATR、MACD、ボリンジャーバンド、出来高比率、ブレイクアウト水準
実ポートフォリオ 現物残高、二つの内部配分、取得原価、含み損益、短期側の損失状況
仮想ポートフォリオ Jev専用の対象資産/法定通貨残高、評価額、累積手数料、取引回数、ドローダウン
制約 手数料率、最小注文額、最大注文額、日次上限、クールダウン

未確定ローソク足を渡さない

現在形成中のローソク足は終値や出来高が確定していません。推論時や後からのバックテストで未来情報(ルックアヘッド・バイアス)を混入させないため、取引所から1本余分に取得して最新の未確定足を除外します。

candles = get_klines(symbol, interval="15m", limit=61)
closed_candles = candles[:-1]

テクニカル指標の計算には十分な過去履歴を与えつつ、Jevへ送る生OHLCV配列は判断に必要な直近本数に絞り込むことで、指標の計算精度とAPI通信量の削減を両立させています。

Jevへの質問は「状態」と「選択肢」を分ける

Jevでは自由文でのプロンプト指示ではなく、状態(state)と質問(questions)を明確に分離したJSONを送信します。choice質問ではアプリケーション側が選択肢を宣言し、レスポンス受信後に型・値域・選択肢の妥当性を検証します。外部APIの応答を無条件に信頼しないことで、機械的に安全な分岐を実現します。

以下は実際に送信しているリクエスト構造の抜粋です(※銘柄名・数値・タイムスタンプは運用情報保護のためダミー値や丸め値を用いています。また、配列データは一部中略しています。APIキーや秘密鍵は当然含めません)。

{
  "model": "jev-1.13.0",
  "state": {
    "generated_at_jst": "2026-..-..T..:..:..+09:00",
    "data_freshness": {
      "market_data_age_seconds": 0,
      "only_closed_candles": true,
      "external_news_included": false
    },
    "strategy": {
      "symbol": "QIITA",
      "market": "Spot",
      "long": "Long-term holding allocation. Not a short sale.",
      "short": "Short-term spot allocation. No leverage or borrowing.",
      "allocation_policy": "Separate internal ledgers in one exchange account.",
      "execution_policy": "Only the trading process validates and places orders. Jev cannot place orders."
    },
    "portfolio": {
      "cycle_number": 1,
      "cycle_base_equity_jpy": 101440.627907,
      "current_equity_jpy": 99500.0,
      "cycle_return_pct": -1.9,
      "free_jpy_total": 49678.622116,
      "free_asset_total": 2.695216,
      "long": {
        "asset_amount": 2.694585,
        "estimated_cost_jpy": 49921.594,
        "unrealized_pnl_jpy": 0.0,
        "available_buy_budget_jpy": 0.0,
        "available_sell_asset": 2.694585
      },
      "short": {
        "asset_amount": 0.000631,
        "jpy_budget": 49678.622116,
        "recent_stop_losses": 2,
        "entry_locked_by_system": true,
        "entry_lock_reason": "Short cycle loss limit reached",
        "available_buy_budget_jpy": 49678.622116,
        "available_sell_asset": 0.000631
      }
    },
    "market": {
      "last_price_jpy": 18000.0,
      "change_24h_pct": 2.1,
      "high_24h_jpy": 18683.0,
      "low_24h_jpy": 17862.0,
      "best_bid_jpy": 17990.0,
      "best_ask_jpy": 18000.0,
      "spread_pct": 0.056,
      "top10_bid_imbalance": 0.35,
      "change_15m_pct": -0.03,
      "change_1h_pct": 0.67,
      "change_4h_pct": 2.50,
      "change_7d_pct": 18.34,
      "timeframes": {
        "15m": {
          "indicators": {
            "ema20": 18399.10,
            "ema50": 18264.23,
            "rsi14": 69.80,
            "atr_pct": 0.00503,
            "macd": 71.2,
            "bollinger_position": 0.91,
            "regime": "TREND_UP"
          },
          "closed_bars_ohlcv": [
            [18000, 18080, 17970, 18040, 123.4],
            [18040, 18120, 18010, 18100, 98.7],
            "... 直近48本の確定15分足を中略 ..."
          ]
        },
        "1h": { "indicators": { "...": "..." }, "closed_bars_ohlcv": ["..."] },
        "4h": { "indicators": { "...": "..." }, "closed_bars_ohlcv": ["..."] },
        "1d": { "indicators": { "...": "..." }, "closed_bars_ohlcv": ["..."] }
      }
    },
    "recent_short_events": ["... 直近の短期トラックイベントを中略 ..."],
    "execution_constraints": {
      "fee_rate_pct": 0.1,
      "max_order_quote_jpy": 60000.0,
      "max_daily_trades": 3,
      "cooldown_seconds": 10800
    },
    "jev_virtual_portfolio": {
      "equity_jpy": 99709.92,
      "return_pct": 0.0,
      "drawdown_pct": 0.0,
      "long": { "asset": 2.694585, "jpy": 0.0 },
      "short": { "asset": 0.000631, "jpy": 49678.622116 }
    }
  },
  "questions": {
    "market_regime": {
      "type": "choice",
      "instructions": "Choose the current market regime using only the supplied state.",
      "criteria": {
        "uptrend": "Uptrend is dominant.",
        "downtrend": "Downtrend is dominant.",
        "range": "Direction is weak and price is contained in a range.",
        "transition": "Timeframes conflict or direction is unresolved."
      }
    },
    "long_position_action": {
      "type": "choice",
      "criteria": {
        "SELL": "The long-holding premise has deteriorated.",
        "HOLD": "Keeping the long allocation is preferable."
      }
    },
    "short_entry_if_unlocked": {
      "type": "choice",
      "criteria": {
        "BUY": "A new short-track spot entry has positive expected value after fees.",
        "HOLD": "Waiting in cash is preferable."
      }
    },
    "virtual_long_action": {
      "type": "choice",
      "criteria": { "BUY": "...", "SELL": "...", "HOLD": "..." }
    },
    "virtual_short_action": {
      "type": "choice",
      "criteria": { "BUY": "...", "SELL": "...", "HOLD": "..." }
    }
  }
}

Jevからのレスポンスは以下の形式で返却されます。

{
  "answers": {
    "virtual_long_action": {
      "type": "choice",
      "choice": "HOLD",
      "confidence": 0.82,
      "probabilities": {
        "BUY": 0.08,
        "SELL": 0.10,
        "HOLD": 0.82
      }
    }
  }
}

実保有向けの質問と仮想台帳向けの質問

実保有の状態に応じた質問では、無意味な選択肢をあらかじめ排除します。例えば長期トラックに買い付け余力がないならHOLDとSELLのみ、短期トラックに売却可能な現物がないならHOLDとBUYのみを提示します。

また、システム側でエントリーがロックされている場合(例: 上記JSONのように短期サイクルの損失限度に到達している状態)でも、質問自体は short_entry_if_unlocked として送信します。発注プロセス側でロックされているため実売買は行われませんが、「もしエントリー可能だった場合、モデルはどう判断していたか」という観測データを欠落させずにログへ残すためです。

一方、Jev専用の仮想台帳に対しては、常に BUY / SELL / HOLD の3択を尋ねます。仮想台帳はJev独自の判断履歴によって資産配分が変化していくため、実保有の制約とは切り離して推論させる必要があるからです。

応答検証は必須

HTTP 200が返ってきたとしても、そのまま盲信することはできません。少なくとも以下のような型・値域チェックを挟みます。

def validate_choice(answer, criteria):
    if answer.get("type") != "choice":
        raise ValueError("invalid response type")
    if answer.get("choice") not in criteria:
        raise ValueError("choice outside declared criteria")
    confidence = answer.get("confidence")
    if not isinstance(confidence, (int, float)) or not 0 <= confidence <= 1:
        raise ValueError("invalid confidence")

さらに、リクエスト単位のタイムアウトを設定し、失敗時は安全にスキップします。

try:
    response = requests.post(url, json=payload, timeout=3)
    response.raise_for_status()
    answers = validate_response(response.json(), questions)
except Exception as error:
    logger.warning("Jev evaluation failed; static trading continues: %s", error)
    answers = None

ここで重要なのは、answers is Noneとなった場合を「何もしない」ではなく、「既存の実取引ロジックを何一つ妨げない」フォールバックとして扱うことです。

Jev専用の仮想台帳

Jevの成績を公正に評価するため、実口座と完全に分離したJSON台帳をメモリおよびファイル上で管理します。今回の運用では、開始時点の二つの内部配分を複製し、以後はJevの仮想判断のみによって台帳を更新していきます。二つのトラックは必須ではなく、単一戦略なら一つの仮想台帳へ置き換えられます。

{
  "symbol": "<REDACTED>",
  "initial_equity": 100000,
  "long": { "asset": 1.23, "cash": 0 },
  "short": { "asset": 0, "cash": 50000 },
  "total_fees": 0,
  "events": []
}

擬似約定は、甘い見積もりを避けるため保守的に評価します。

  • 買い: 最良売り気配(ask)で約定
  • 売り: 最良買い気配(bid)で約定
  • 手数料: 約定ごとに確実に控除
  • 注文サイズ: 設定された最小・最大注文額を強制適用
  • 頻度制限: 日次上限とクールダウン時間を適用
  • 保有制限: 保有している現物残高を超えて売却しない

擬似約定のコアロジックは以下の通りです。

if action == "BUY":
    quote = min(track.cash, max_order_quote)
    amount = quote / (ask * (1 + fee_rate))
    track.cash -= quote
    track.asset += amount

if action == "SELL":
    gross = track.asset * bid
    fee = gross * fee_rate
    track.cash += gross - fee
    track.asset = 0

Note: 上記コードは、クォート通貨(JPY)側で手数料を精算する取引所モデルの近似です。購入した仮想通貨(base asset)側から手数料が引かれる取引所の場合は、受取数量側で減算処理を行ってください。

ログ形式と後からの検証

意思決定ログはJSON Lines形式で出力します。1行1イベントの構造にしておくことで、NASへの追記が容易になり、jq、Python、DuckDBなどを用いた事後分析が格段にしやすくなります。

{
  "event": "decision",
  "decision_id": "...",
  "created_at": "...",
  "answers": { "...": "..." },
  "virtual_execution": {
    "executions": [],
    "portfolio": {
      "equity": 100123,
      "return_pct": 0.12,
      "drawdown_pct": -0.35,
      "total_fees": 42,
      "trade_count": 3
    }
  }
}

意思決定直後の評価だけでなく、15分、1時間、4時間、24時間後の市場価格もログへ追記して将来リターンを算出します。

forward_return = (outcome_price / decision_price - 1) * 100

ただし、将来リターンは単発の判断精度を測る参考値に過ぎません。取引戦略の成否は、売買頻度に伴うスプレッドや手数料の累積控除、そしてドローダウンに耐えうるかどうかにかかっています。そのため、最終的な評価は仮想台帳における資産推移とリスク指標をベースに行います。

confidenceをどう扱うか

Jevは選択肢とともにconfidence(確信度)や確率分布を返します。しかし、この数値を信じて発注ロットを変動させたり、緊急停止のトリガーにしたりする設計にはしていません。

モデルが出力する確信度は「提示されたコンテキストにおいてモデル自身がどれだけ自信を持っているか」の相対的な観測値であり、市場における真の勝率とイコールではないためです。信頼度は判断の傾向を分析するためのログデータとして蓄積し、執行ロジック自体はあくまで定量的・機械的なルールを貫きます。

運用上のチェックリスト

本システムの実装と運用において、安全性を担保するために設けている境界線です。

  1. 認証情報の隔離: APIキーは環境変数等で管理し、プロンプト入力やログへ混入させない
  2. 取引所権限の最小化: 取引所APIキーには出金権限を絶対に付与しない
  3. 推論と発注の完全分離: AIのレスポンスを発注関数へ直接バインドしない
  4. 取引所制約の再検証: 最小数量・金額、呼値、手数料を発注直前のプロセス側で必ず検証する
  5. 外部依存のフォールバック: 市場データ取得失敗、時計ズレ、推論APIタイムアウトを「日常茶飯事のエラー」として処理し、メインループを止めない
  6. 十分なシャドー期間: 実運用への組み込みを判断する前に、異なる相場環境を網羅するテスト期間を確保する
  7. 複合指標での評価: 単純な勝率だけでなく、手数料込みの資産曲線や最大ドローダウンで総合評価する

おわりに

自由文を生成する汎用LLMと異なり、Jevのような構造化意思決定APIは、事前に定義した選択肢とクライアント側の応答検証を組み合わせやすく、既存システムへ組み込む際の分岐を明示できます。

しかし、どれほどモデルが明晰な判断を返したとしても、実運用の世界では手数料、スプレッド、注文制約といった摩擦が利益を削ります。だからこそ、AIの判断を安易に実弾発注へ直結させず、手数料・スプレッド・注文制約を明示的にモデル化した仮想台帳で並走させる必要があります。

Jevの活用を検討している方や、AIを既存の自動売買システムへ安全に組み込みたい方の参考になれば幸いです。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?