Bot CEX 開発ログ

🛠️開発記録#467(2026/2/28)「裁定は取らない ― 世界価格を“状態量”に変えるまで」

王道の裁定はやらない。以前にCEX間のシンプルな裁定を試そうとした時、個人で戦うには執行コストが重たいということがすでにわかっていたからです。
そこで今回は、最初から、他市場の価格を“抜く”のではなく、主戦場である国内市場の理解に使うつもりで開発を始めました。

また、単一市場の板や約定データを掘り続けても、どうしても残る違和感もありました。歪みは見える。状態も観測できる。しかし、時間幅を伸ばして観測していくに従って、それが「市場全体の動き」なのか、「その市場固有の偏り」なのかが分かりにくくなります。内側だけを見ていても、基準がないのです。変数が増えるので当然と言えば当然なのですが。

そこでまず導入したのが、世界価格という外部基準です。
複数の海外市場を統合した“world proxy”を作り、それと国内価格との差を計測する。ここで重要なのは、その差を利益として直接取ることではありません。その差を、国内市場の状態を示す指標――すなわち「状態量」として扱うことです。

価格差は鞘ではない。
持続時間、傾き、レジーム。その振る舞いこそが意味を持つ。

この記事では、1+N市場観測機の設計意図と現在地、そして premium を「利益」から「状態量」へ再定義するまでの思考整理を書きます。まだ優位性は証明されていません。しかし、少なくとも方向性ははっきりしました。構造があるかどうかは、これからデータで白黒をつけます。

1. 単市場だけでは足りないと感じた理由

これまで私は、主戦場である国内市場の板と約定データを徹底的に観測してきました。
OBI、OFI、持続時間、約定の連続性。ログを取り、分類し、負け構造を固定化しようと試みました。

歪みは確かに見えます。
板が偏る瞬間も、フローが加速する局面も、統計的に捉えることはできます。しかし、その歪みが「市場全体の動き」によるものなのか、それとも「国内市場固有の偏り」なのかが分からない。ここに違和感が残り続けました。

例えば、国内価格が急伸したとき。それは世界価格が先に動いた結果なのか。それとも、国内参加者のレバレッジ需要が独自に価格を押し上げているのか。単市場のデータだけでは、この区別ができません。内部の情報だけをどれだけ精緻に観測しても、外部基準がなければ、状態の意味づけが曖昧なままなのです。

内側の歪みを拾おうとするほど、むしろ「基準の不在」が浮き彫りになりました。
価格が動いた事実は分かる。しかし、その動きが“追随”なのか、“主導”なのかは分からない。この区別がつかないままでは、戦略を一段引き上げることはできません。

単市場分析は無意味ではありません。ただ、それだけでは足りなかった。
国内市場を理解するには、国内市場の外側を同時に見る必要がある。そう考えたのが、今回の観測機開発の出発点です。

2. 他市場観測は“裁定”のためではない

ここで強調しておきたいのは、今回作ったマルチ市場観測機は、いわゆる「アービトラージ(裁定)」を実行するためのものではない、ということです。

確かに構造は裁定botに似ています。
今回作成したmulti_market_probe_v1.py では、base市場(bf_fx)と複数のreference市場を並列にポーリングし、ToB(best bid / best ask)を正規化し、JPYへ統一してから executable ベースの差分を計算しています。

exec_ab_raw = base.bid - ref_ask_jpy
exec_ba_raw = ref_bid_jpy - base.ask
exec_best_bps = max(exec_ab_bps, exec_ba_bps)

この時点だけを見ると、「両建てして抜けそうな鞘を探している」ように見えるかもしれません。しかし実際にやっているのは、その差分を即時執行に使うことではありません。

config.yamlmulti_market_probe_v1 では、world proxy を明示的に定義しています。

world_proxy:
enabled: true
method: median
reference_markets:
- bf_spot
- binance_perp
- bybit_perp
- coinbase_spot

さらに、国内市場を除いた world_proxy_global も定義しています。

world_proxy_global:
enabled: true
reference_markets:
- binance_perp
- bybit_perp
- coinbase_spot

これは「どこが一番高いか」を探しているのではなく、「世界価格という合成基準」を作っているということです。

そして multi_market_probe_service.py では、歪みを検知した瞬間に注文を出すのではなく、PREMIUM_STATE というイベントとして状態を吐き出しています。

_emit_event(
{
"event_type": "PREMIUM_STATE",
"premium_exec_best_bps": ...,
"premium_persistence_sec": ...,
"premium_slope_bps_per_s": ...,
"premium_regime": ...,
}
)

ここで重要なのは、premium を

  • 持続時間(persistence)
  • 傾き(slope)
  • レジーム(DOMESTIC_LED / GLOBAL_LED / REVERSION_PHASE)

という「状態量」に変換している点です。

裁定botであれば、

差分が閾値を超えた → 即両建て

という流れになります。

しかし今回の設計では、

差分がどういう状態で、どのくらい続き、どの方向に変化しているか

を観測し、ログとして蓄積することを目的にしています。

その証拠に、将来リターン検証スクリプト analyze_premium_state_future_returns.py では、PREMIUM_STATE と将来の bf_fx のリターンを突き合わせて、説明力を評価する設計になっています。

つまり、やっていることは「鞘を抜く」ことではなく、

premium という外部基準が、国内市場の将来挙動をどれだけ説明できるかを検証する

ことです。

この違いは大きい。

裁定は、速度と資本の勝負です。
状態量は、構造の理解の問題です。

私が作っているのは前者ではなく、後者です。

他市場観測は、利益を直接生む装置ではありません。
主戦場である国内市場の状態を、外部基準で測るための補助輪です。

裁定ではなく、構造の計測。

ここを取り違えると、この観測機の意図はまったく別のものになってしまいます。

3. 世界価格という外部基準

単市場を見続けていて一番苦しかったのは、「(相対的な)基準がない」ことでした。

価格が動く。板が傾く。OFIが偏る。
しかし、その動きが“市場全体の自然な変動”なのか、“この市場だけの歪み”なのかを判断する物差しが弱い。

そこで導入したのが、世界価格という外部基準です。


world proxy という考え方

今回の観測機では、海外複数市場の価格を合成して「world proxy」を作っています。

実装上は、各 reference 市場の ToB を JPY に統一し、その中央値(または平均)を世界価格とみなします。

mid_jpy = self._aggregate_world_values(mids, method=method)

この world proxy は、特定の取引所の価格ではありません。
Binance、Bybit、Coinbase などを統合した、いわば「合成された世界価格」です。

world_proxy:
method: median
reference_markets:
- bf_spot
- binance_perp
- bybit_perp
- coinbase_spot

さらに、国内市場を除外した world_proxy_global も用意しています。

world_proxy_global:
reference_markets:
- binance_perp
- bybit_perp
- coinbase_spot

これは意図的です。
国内価格を world に混ぜると、「国内の偏り」を基準の中に吸収してしまう可能性があるからです。


なぜ外部基準が必要なのか

例えば、bitFlyer FX が急騰したとします。

単市場で見れば、

  • 板が薄い
  • OFIが偏っている
  • 約定が連続している

といった現象が観測されるでしょう。

しかし、それが

  • 世界価格が先に動いた結果なのか
  • 国内だけが過熱しているのか

は分かりません。

world proxy を入れることで、

premium = domestic_price - world_price

という差分が定義できます。

この差分がゼロ付近なら、国内は単に世界に追随しているだけです。
しかし差分が拡大し、持続するなら、それは「国内主導」の状態かもしれない。

外部基準があることで、初めて

  • 追随か
  • 主導か
  • 過熱か
  • 収束か

という分類が可能になります。


premium は基準からの偏差

multi_market_probe_v1.py では、world proxy と base 市場の executable 差分を premium として計算しています。

exec_best_bps = max(exec_ab_bps, exec_ba_bps)

ここで得られるのは、「抜ける鞘」ではなく、「基準からの偏差」です。

そしてその偏差を、

  • premium_persistence_sec
  • premium_slope_bps_per_s
  • premium_regime

といった状態量に変換しています。

外部基準があるからこそ、この状態化が可能になります。


世界価格は“正解”ではない

ここで誤解してはいけないのは、world proxy が「真の価格」だというわけではない、という点です。

世界価格もまた、参加者の集合的な結果に過ぎません。

しかし、

国内市場の外側にある価格

という意味で、十分な基準になります。

単市場だけを見ていると、「その市場内での相対的な歪み」しか分かりません。
world proxy を導入することで、「市場間の相対位置」という次元が追加されます。

この一段上の視点が、今回の観測機の核心です。


単市場の分析は、内側からの視点。
世界価格の導入は、外側からの視点。

この二つが揃って初めて、国内市場の状態を立体的に捉えられるようになると考えています。

4. premiumを“鞘”から“状態量”へ

最初に明確にしておきたいのは、premiumは「抜くべき鞘」ではない、ということです。

multi_market_probe_v1.py では、base市場(bf_fx)と参照市場の executable 差分を bps で計算しています。

exec_ab_raw = base.bid - ref_ask_jpy
exec_ba_raw = ref_bid_jpy - base.ask
exec_best_bps = max(exec_ab_bps, exec_ba_bps)

ここで得られる exec_best_bps は、いわば「理論上の最大鞘」です。
しかし、この値を見た瞬間に両建てを考えるのは、今回の設計思想とは違います。

なぜなら、この差分は瞬間値であり、構造を含んでいないからです。


瞬間値としてのpremiumの限界

premiumが +8bps 出たとします。

それが、

  • 0.3秒だけのノイズなのか
  • 10秒持続する偏りなのか
  • さらに拡大している途中なのか
  • 収束に転じているのか

は、この1点の数値からは分かりません。

裁定思考は、

premium >= 閾値 → エントリー

という二値判断になります。

しかし、今回欲しいのは二値ではありません。


premiumを状態へ変換する

そこで premium_state レイヤーを設けています。

premium_state:
premium_trigger_bps: 4.0
persistence_min_sec: 2.0
reversion_slope_bps_per_s: 0.05

さらに、実装側では premium を時間軸上で追跡し、

  • premium_persistence_sec
  • premium_slope_bps_per_s
  • premium_regime

を算出しています。

premium_persistence_sec, premium_slope_bps_per_s = self._update_premium_state(...)
regime, regime_code = self._classify_premium_regime(...)

ここでpremiumは単なる価格差ではなく、

  • どの方向に
  • どれくらいの時間
  • どの速度で

偏っているか、という動的な量になります。

これが「状態量」としてのpremiumです。


premium_regimeという概念

premiumはさらに、レジームとして分類されます。

  • DOMESTIC_LED
  • GLOBAL_LED
  • REVERSION_PHASE
  • NEUTRAL

これは単なるラベル付けではありません。

例えば、

  • premiumが拡大し、世界価格は横ばい → DOMESTIC_LED
  • premiumが大きく、傾きが逆転 → REVERSION_PHASE

というように、状態遷移として扱います。

つまりpremiumは、

価格差そのものではなく、価格差の振る舞い

を指す概念へと再定義されています。


“鞘”から“温度”へ

裁定的な見方では、

  • premiumは利益の源泉
  • 大きいほど良い

という単純な解釈になります。

しかし状態量として見るなら、

  • premiumが拡大しているのか
  • 安定しているのか
  • 崩壊しているのか

が重要になります。

ここでpremiumは、利益ではなく「温度計」になります。

国内市場が世界より過熱しているのか。
それとも世界に追随しているだけなのか。

premiumの値よりも、その持続時間と傾きが意味を持ちます。


premiumはトリガーではなく説明変数

将来リターン検証スクリプトでは、PREMIUM_STATE と未来の bf_fx リターンを突き合わせています。

目的は、

premiumが将来価格をどれだけ説明するか

を測ることです。

premiumはエントリー条件そのものではなく、
「そのとき市場がどういう状態だったか」を表す説明変数になります。

これが、今回の再定義の核心です。


価格差は鞘ではない。
持続時間、傾き、レジーム。その振る舞いこそが意味を持つ。

premiumを利益から切り離し、状態へと変換する。
それが今回の観測機でやっていることです。

5. 1+N市場観測機の現在地

ここまでで、観測機としての「骨格」は一通りできました。

現在の構成は、単なる価格差計算ツールではありません。
複数市場を統合し、世界価格を合成し、premiumを状態量に変換し、それをイベントとして記録する観測装置です。


① 市場取得レイヤー

multi_market_probe_v1.py では、各市場を非同期でポーリングし、ToBを正規化しています。

  • base:bf_fx
  • refs:bf_spot / binance_perp / bybit_perp / coinbase_spot
  • fx:usd_jpy

USD建て市場は JPY に換算してから統一空間に投影します。

if ref.quote_currency == "USD":
ref_mid_jpy *= fx_rate
ref_bid_jpy *= fx_rate
ref_ask_jpy *= fx_rate

ここでようやく、「同一資産・同一通貨」の比較が可能になります。


② world proxy(世界価格合成)

設定ファイルでは、world proxy を明示的に定義しています。

world_proxy:
enabled: true
method: median
min_refs: 2

複数のreference市場から中央値を取り、単一市場依存を避ける設計です。

さらに、

world_proxy_global:
reference_markets:
- binance_perp
- bybit_perp
- coinbase_spot

国内市場を除いた「純粋なグローバル基準」も用意しています。

これは、

  • 国内主導か
  • 世界主導か

を区別するための基礎になります。


③ premium状態化

premiumは単なる差分ではなく、状態として管理されています。

  • premium_exec_best_bps
  • premium_persistence_sec
  • premium_slope_bps_per_s
  • premium_regime
premium_persistence_sec, premium_slope_bps_per_s = self._update_premium_state(...)
regime, regime_code = self._classify_premium_regime(...)

これにより、premiumは

  • 瞬間値
    ではなく
  • 時系列的な状態

として扱われています。


④ FXガードと品質管理

FX依存比率が高いのにFXが古い場合、状態を無効化するガードも入っています。

fx_guard:
enabled: true
dependent_ratio_threshold: 0.5

さらに、

  • stale_flag
  • recv_lag
  • feed_health

などのメトリクスも Prometheus 経由で監視できます。

これは単なる価格監視ではなく、「観測機そのものの健全性」を可視化する設計です。


⑤ ログと将来リターン検証

観測結果は PREMIUM_STATE としてイベント化されます。

その後、analyze_premium_state_future_returns.py で future return と突き合わせることができます。

directional_exec_move_bps
directional_exec_move_positive_rate_pct

つまり、

観測 → 状態化 → 将来リターン検証

まで一気通貫でつながっています。


現在地の正直な評価

  • 観測レイヤー:完成
  • 状態化レイヤー:完成
  • 将来リターン検証:可能
  • 実行ロジック:未導入

いまはまだ「戦略」ではありません。

premiumが本当に説明力を持つのかどうかを、ログで白黒つける段階です。

しかし、単市場で歪みを追っていた頃とは明確に違います。
世界基準を導入し、状態として扱い、実行可能性を意識した設計に変わりました。

ここまで来たことで、ようやく「構造があるかどうか」を測れる土台が整ったと言えます。

6. まだ戦略ではない

ここまで書いてきた内容を見ると、いかにも「次はトレードだ」と言いたくなるかもしれません。
world proxy を作り、premium を状態量に変換し、レジーム分類までできている。構造は整っているように見えます。

しかし、正直に言えば、これはまだ戦略ではありません。

いま手元にあるのは、あくまで観測装置です。


状態があることと、利益が出ることは別

premium が拡大している。
premium が持続している。
premium が収束に転じた。

こうした状態を分類できることと、それが利益に結びつくことは全く別の話です。

これまでの経験で、私は何度もこの錯覚に引っかかってきました。

  • 歪みが見える
  • 構造が言語化できる
  • ロジックが綺麗に整理できる

それでも、PnL はマイナスになる。

「説明できる」ことと「稼げる」ことの間には、明確な断絶があります。


premiumはまだ“説明変数”に過ぎない

現段階でできているのは、

PREMIUM_STATE と将来リターンを突き合わせること

だけです。

将来リターン検証スクリプトで、

  • directional_exec_move_bps_mean
  • positive_rate

といった統計値を出せるようにはなっています。

しかし、これは「説明力があるかどうか」を見る段階です。

まだ、

  • エントリー閾値
  • 保有時間
  • exitロジック
  • 実行コスト込みの期待値

は固定していません。

固定していないというより、固定してはいけない段階です。


いまやるべきは“白黒”

戦略にしたくなる気持ちはあります。

A(拡大に乗る)
B(極端からの収束を取る)

どちらもロジックとしては組めます。

しかし、premiumが将来価格に対して統計的な偏りを持たないのであれば、そこにいくらロジックを積んでも意味がありません。

いまはまだ、

  • premiumは存在するのか
  • premiumは持続するのか
  • premiumは将来を説明するのか

を確認する段階です。

ここでダメなら、迷わず次に行く。

その自覚が必要です。


これは“研究フェーズ”

今回の観測機は、戦略の土台ではありますが、まだ武器ではありません。

  • 武器になる可能性はある
  • しかし、証明はこれから

この距離感を保てるかどうかで、次の一歩の質が変わります。

焦って実装に進むよりも、

構造があるかどうかを最短で確かめる

それが、いまのフェーズで最も合理的な行動だと考えています。

premiumは魅力的です。
しかし、魅力と優位性は違う。

それを忘れないための、自分へのメモでもあります。

7. AかBかを決めない理由

premiumを状態量として扱う以上、自然と二つの戦略仮説が浮かびます。

A:premiumが拡大している最中に乗る
B:premiumが極端になった後の収束を取る

どちらもロジックとしては成立しています。実装も可能です。実際、現在の観測機は両方を識別できるように設計しています。

multi_market_probe_v1.py では、premiumの持続時間と傾きを追跡し、

premium_persistence_sec, premium_slope_bps_per_s = self._update_premium_state(...)
regime, regime_code = self._classify_premium_regime(...)

さらに、DOMESTIC_LEDREVERSION_PHASE といったレジーム分類まで行っています。

設計上は、AもBもすぐに実装できる状態です。

それでも、いま決めない理由があります。


1. 仮説は“選ぶ”ものではなく、“残る”もの

AもBも、理屈としては美しい。

  • premiumが拡大しているなら、国内フローに乗ればよい
  • premiumが極端なら、収束を狙えばよい

しかし、美しいロジックは、往々にして市場では通用しません。

どちらが正しいかは、議論ではなくデータでしか決まりません。

premiumの将来リターンへの説明力を確認する前に、どちらかを選ぶのは早すぎます。


2. 市場は一方向に単純ではない

市場は常に

  • 拡大が続く局面
  • 極端からの収束が起きる局面

の両方を持っています。

ある時間帯ではAが有効で、別の時間帯ではBが有効かもしれない。

premiumの挙動自体がレジーム依存である可能性も高い。

その段階で「Aに賭ける」「Bに賭ける」と決めてしまうのは、構造を十分に見ていないということになります。


3. いまは探索フェーズ

現在の観測機は、

  • premium_exec_best_bps
  • premium_persistence_sec
  • premium_slope_bps_per_s
  • premium_regime

をログとして出力し、

analyze_premium_state_future_returns.py で将来リターンと突き合わせることができます。

つまり、

状態 × 将来リターン

の分布を見てから決めることができます。

この環境が整っている以上、仮説を先に固定する理由はありません。


4. 早く決めることは、強さではない

戦略を早く決めることは、決断力のように見えるかもしれません。

しかし、検証可能な環境があるにもかかわらず仮説を固定するのは、単なる先入観です。

これまでの開発でも、

  • 閾値を決め打ちした
  • パラメータを微調整した
  • ロジックを先に完成させた

結果、後から否定することになった経験が何度もあります。

今回は同じ轍を踏まないようにしたい。


結論

AかBかを決めないのは、優柔不断だからではありません。

premiumが本当に優位性を持つなら、統計上どちらか(あるいは両方)が自然に浮かび上がるはずです。

いまは選ぶ段階ではなく、削る段階。

構造がなければ撤退する。
構造があれば、そのとき初めて戦略にする。

それが、いまのフェーズでの最も合理的な態度だと考えています。

8. 明日やること

ここまでで、観測機は一通り完成しました。
しかし、設計が整ったことと、優位性があることはまったく別です。

明日やることは、戦略の実装ではありません。
観測機そのものの健全性と、データの質を確認することです。


1. ログの品質チェック

まず確認するのは、「ちゃんと観測できているか」です。

  • PREMIUM_STATE は十分な頻度で発生しているか
  • world_ref_count は安定しているか
  • stale_flag が常に立っていないか
  • fx_guard_invalid_flag が頻発していないか

premiumを使う前に、premiumがまともに計測されているかを確認します。

観測機が不安定な状態で、戦略の是非を議論しても意味がありません。


2. premiumの分布を見る

次に見るのは、premiumそのものの分布です。

  • premium_exec_best_bps のヒストグラム
  • premium_persistence_sec の分布
  • premium_slope_bps_per_s の分布

もしpremiumが常に ±2bps の範囲に収まっているなら、それは構造ではなくノイズかもしれません。

逆に、一定の閾値を超えた状態が持続しているなら、そこに意味がある可能性があります。

まずは「存在するのかどうか」を確認します。


3. 状態 × 将来リターンの確認

最後に、PREMIUM_STATE と将来リターンを突き合わせます。

特に見るのは、

  • directional_exec_move_bps_mean
  • directional_exec_move_positive_rate_pct

です。

premiumの符号に沿った方向で、将来リターンが偏っているかどうか。

ここで偏りが出なければ、AもBも成立しません。


4. 判断基準

明日の目的は、戦略を作ることではありません。

  • premiumが構造として存在するか
  • premiumが将来価格を説明できるか

この二点を確認することです。

もし説明力がなければ、迷わず次の戦略に進みます。
もし兆しがあるなら、そのとき初めて実行ロジックを検討します。


焦らない。
積み上げる。
そして、ダメならすぐ切る。

明日はそのための一日です。

-Bot, CEX, 開発ログ