日曜の夜にノートブックを送ってくれて以来、ずっとサブモニターに開いたままです。横断的モメンタム、Binance USDⓈ-Mの30日間ドル建て出来高上位150銘柄の無期限先物、週次リバランス、上位10%をロング、下位10%をショート、期間は2021-01から2026-06。Sharpe 2.31、最大ドローダウン14.2%。コストモデルも妥当に見えました。takerでエントリーし、takerで決済、各区間のfundingを計上し、板に対する注文サイズに応じてスリッページを加える。難しいところはきちんとできています。それなのに、6週間のペーパートレードが横ばいなのはなぜか、エッジが減衰したのか、と聞かれました。
減衰したわけではありません。そもそもバックテストに存在していなかったのです。セル4を見てください:
info = requests.get(BASE + "/fapi/v1/exchangeInfo").json()
symbols = [s["symbol"] for s in info["symbols"]
if s["status"] == "TRADING" and s["quoteAsset"] == "USDT"]
このエンドポイントを2026年6月に呼び、その結果を使って2021年3月に取引できた銘柄を定義しています。そのリストにある銘柄は、構造上すべて2026年6月まで生き残ったものです。これがバグの全容です。ほかのコストモデルを全部合わせたよりも、ずっと大きな問題です。
エンドポイントが教えてくれないこと
exchangeInfoに履歴はありません。asOfパラメーターも、アーカイブも、変更履歴もありません。あるのは今この瞬間のスナップショットだけで、Binanceはそれ以上のものを約束したことはありません。コントラクトが上場廃止になると、そのエントリーはレスポンスから削除され、API上ではティッカーも存在しなくなります。Binanceは2019年以降、USDⓈ-M無期限先物を600銘柄以上上場させ、そのうち100銘柄を優に超える数を終了させています。BTS、COCOS、TOMO、RAY、FTT、SC。2021年のアルトシーズンに登場し、輝かしい四半期を1度迎えたあと、取引が細って取引所に廃止された銘柄がほかにも多数あります。
30日モメンタムのスクリーニングがどんな銘柄を好むか、考えてみてください。BTCではありません。上場時の急騰とTwitterの話題で、直近3倍になった銘柄です。その銘柄群と最終的に上場廃止になる銘柄群は大きく重なります。そして、あなたのユニバースフィルターはその重なりを取り除いていました。
スナップショットのアーカイブを使い、ポイントインタイムのユニバースで、シグナルとコストは同じまま再計算しました。Sharpeは0.74、ドローダウンは31%になりました。差の約5分の2は、保有する機会すらなかった上場廃止銘柄によるものです。さらに4分の1は、その裏返しにあたる問題です。こちらは少し見えにくく、おそらくあなたにはもっと気に入らないでしょう。
タイムマシンのもう一方:まだ存在していなかった銘柄
特徴量は90日リターンです。ローリングウィンドウにはmin_periods=20を設定しています。サンプルの最初の四半期がウォームアップで吹き飛ばないよう一度設定して、その後見直していません。すると、上場から21日しか経っていないコントラクトも、上場後3週間分の価格変動だけでモメンタムスコアが計算されます。上場がうまくいけばたいてい上位10%に入り、ポートフォリオに組み込まれます。
この点は、正当と言えなくもありません。問題はこちらです。データプロバイダーが、一部の銘柄について無期限先物の上場前の現物またはインデックスデータを補完しているため、いくつかのコントラクトで、自身のonboardDateより前にさかのぼるkline履歴が存在します。確認には1分もかかりません。価格テーブルを上場日と結合して、上場前の行数を数えてください。あなたのデータには、上場前のバーがある銘柄が41あります。そのうち1銘柄は2023年の夏、まだ9日間は存在しないはずのコントラクトのポジションから、エクイティカーブに1週間で6%の利益を加えています。
ティッカー文字列は識別子ではありません。取引所が貸し出すラベルで、同じものが2度使われることもあります。
ここで名称変更の問題も出てきます。MATICUSDTはPOLUSDTになりました。FTMUSDTは1:1の交換でSUSDTになりました。2022年5月のLUNAの件では、LUNCUSDTが残り、その後、ほぼすべての価値を失った銘柄とティッカーの語幹を共有する、新しいLUNAUSDTが登場しました。ローダーがシンボル文字列をキーにして見つけたファイルを連結しているなら、少なくとも1つの系列に、価格変動ではない不連続が含まれています。モメンタム特徴量はその不連続を、横断面で最も強いシグナルとして読み取ってしまいます。
ほかにもじわじわ変わるもの
銘柄リストが時間とともに変わると認めれば、同じ話はレスポンス内のほかのすべてのフィールドにも当てはまります。あなたはそのどれにも今日の値を使っています。
| フィールド | どう変わるか | 何が問題になるか |
|---|---|---|
| status | TRADING → SETTLING → 消滅 | サバイバーシップバイアス;実際には取引されていない終値で決済したことになる |
| onboardDate | 毎週新規上場;終了した銘柄には値がない | 存在前のコントラクトを取引してしまう |
| tickSize / stepSize | 価格水準の変化に応じて調整 | 注文の丸め処理と、当時なら拒否されていた指値 |
| minNotional | 板が薄い銘柄では時間とともに引き上げ | ライブの注文ルーターなら拒否する小さなポジション |
| fundingIntervalHours | 長年8hだったものが、多くの銘柄で4hや1hに | まさにスクリーニングで選ばれるアルトで、キャリーの計算が2〜3倍ずれる |
| leverage brackets | ティアと維持証拠金が改定される | 清算のシミュレーションと証拠金の上限 |
あなたのケースでは、fundingのずれが最も痛いです。計上ループは、全期間にわたって1日3回の支払いを想定しています。アルトのポートフォリオでは、かなりの銘柄が4時間ごとのfundingに移行しています。シミュレーション上の損益の多くは、funding率が高い銘柄のショートから生じています。端数処理の誤差ではありません。係数が違うほどの誤差です。
上場廃止そのものがイベントなのに、モデル化されていません
ポイントインタイムでの再計算では、戦略にかなり有利な決済条件を与えました。実際の上場廃止は、決まった手順で進みます。まず告知があり、通常7〜14日後にreduce-onlyの期間に入り、その後マーク価格で強制決済されます。告知は公開情報なので、対応できます。正直なシミュレーションなら告知日の終値で決済します。ただし、よくあるアルトの上場廃止では、その終値はすでに前週比で10-20%下落しており、板も薄くなっています。スリッページの計算では、流動性環境が別物になったことを反映する必要があります。約定エンジンが価格影響なしで決済価格を与えているなら、ひそかに、消滅する資産を適正価格で取引できることにしてしまっています。
exchangeInfoを一度もアーカイブしていなくても、手詰まりではありません。 公開ダンプのdata.binance.vision/data/futures/um/monthly/klines/には、APIが忘れたあとも、上場廃止銘柄のディレクトリが長期間残っています。ディレクトリ一覧を取得し、各銘柄について最初と最後の月次ファイルを調べれば、ベンダーのデータなしでも、上場時期と廃止時期の実用的な推定ができます。これは再構成であって記録そのものではなく、tick sizeやfunding間隔までは復元できません。それでも、どの銘柄がいつ存在したかは分かります。月曜に必要な情報の80%はこれで得られます。
シグナルに再び手を入れる前に作ってほしいもの
- 調査対象の各取引所からexchangeInfoを毎日取得し、日付をキーにしてオブジェクトストレージへ書き込むcron。gzip圧縮すれば半メガバイト未満です。10年分でもS3のコストは端数程度で、あとから買うことのできないリサーチ能力が手に入ります。
- そのスナップショットから作るアセットマスター:(venue, symbol, valid_from, valid_to)ごとに1行、全フィールドを保持します。連続するスナップショットの差分から生成し、フィールドが1つでも変わったら新しい行にします。
- タイムスタンプ引数を必須にしたユニバース関数。
universe(ts)を使い、universe()は使いません。デフォルトでは実行できないようにして、未来のあなたも、午前1時のあなたも、誤って現存銘柄のリストを使えないようにしてください。 - データローダーに上場前チェックを追加します。バーは
onboard_tsの1日前より前には存在できないようにしてください。警告ではなく、実行を失敗させます。 - 名称変更後も変わらない安定した内部銘柄IDを用意し、ティッカーは属性として扱います。POLとMATICを同じIDに割り当て、額面変更にはフラグを付けて、連続性の処理で接続を拒否できるようにします。
これを実装して再実行してください。結果は私の出した0.74に近づくと思います。次に考えるのは、消滅した銘柄を含む0.74に、ペーパートレードする価値があるかどうかです。あるかもしれません。無期限先物の横断的モメンタムは無意味ではありませんし、ユニバースを修正したあとに残るものの一部は、ショート側の本物のキャリーです。ペーパートレードとバックテストの結果も一致し始めるでしょう。ペーパートレードはずっとポイントインタイムのユニバースで動いていました。選択の余地はなかったのです。
追伸。これは暗号資産特有の問題ではなく、ここでは目立ちやすいだけです。株式のリサーチでは、CRSPがデータ提供を始めた頃から、上場廃止リターンや使い回されるティッカーに取り組んできました。予測市場ではさらに極端です。すべてのコントラクトが設計上満期を迎えるため、ユニバースは上場と消滅だけでできています。このスクリーニングをKalshiに移植するなら、まずアセットマスターを作ってください。そこには、それ以外の履歴はありません。
← 記事一覧

