パリティ・コーパスの参照ストラテジーは、6月末までに167件から252件に増えた。だが、コーパスでは一度も踏まれなかった3つのバグが、コミュニティで共有されたスクリプトで表面化した。マージンコールによる強制清算の考慮漏れ、FXレートを無視したaffordabilityチェック、そしてサイレントに一度もarmされなかったトレーリングストップだ。
この記事の数値は2026年6月29日に計測したもので、当時のまま掲載しています。最新の結果はパリティの測定方法をご覧ください。
前回コーパスを記事にまとめた5月の時点で、パリティ・コーパスには参照ストラテジーが167件あり、そのうち165件がTradingViewに対してトレード単位の厳密なパリティに達していた。コーパスはその後も増え、6月末の時点で252ストラテジー、うち251件が「excellent」パリティ(99.6%)、ドキュメント化済みのアノマリーが1件、failはゼロになっていた。
見出しの数字は退屈な部分だ。面白いのは、コーパスがカバーしていなかった部分のほうだ。TradingViewブローカーの挙動のうち、我々がモデル化していなかったものが3つあった。いずれも、252件のどれも通らなかったコードパスを、コミュニティで共有されたスクリプトが通ったことで見つかったものだ。本稿ではその3つを順に見ていく。
2026-06-29時点のコーパス
corpus/validation_report.md (2026-06-29)
Total probes: 252
Excellent: 251 (99.6%)
Anomaly: 1
Strong/Moderate/Weak/Minimal/Missing: 0
TV trades: 389,590
Engine trades: 389,688
Matched: 389,468
コーパス全体で、エンジンはTradingViewより98件多くトレードを出力していた。参照トレード389,590件に対して0.025%の差だ。これは集計値であり、ストラテジー単位で見ると、後述する1件のアノマリーによる分はそのうち1トレードだけだった。残りは23件のストラテジーによるものだが、このレポートが採用していた評価基準はトレード件数の差を1%未満まで許容していたので、これらはexcellentと判定されたままだった。大半は1〜2トレードのずれで、98件のうち90件は4件のストラテジーに集中していた。
当時最も新しかった6件のプローブは drawing カテゴリで、Pineの描画オブジェクト(line.get_price、label/boxのジオメトリ)をチャート上の注釈としてだけでなく、バックテスト時のデータとして読み取るランタイムを出したときに追加したものだ。描画した線を計算上の状態として使うストラテジー、たとえば手動でアンカーしたトレンドラインを動的なサポートレベルとして使うようなものでは、スクリプトが既に削除したオブジェクトについても、TradingViewと同じようにエンジンがジオメトリを返し続ける必要がある。6件ともexcellentと判定された。
唯一のアノマリー、隠さず明記する
一致しなかった1件のストラテジーを、フィルタで除外することはしなかった。anomaly-equity-mirror-strategy-equity-01 は、マージンによるエントリー許可判定における1倍equityの境界ちょうどに位置する。当時の我々の分析(コーパスのノートでは確信度95%以上と評価している)では、TradingViewのブローカーエミュレータはまさにこの境界で非単調に振る舞っていた。equityを超えるエントリーの一部を通し、equityに収まるエントリーの一部を拒否し、しかもbarごとに一貫しなかった。我々のエンジンは決定論的で、equityが同じなら許可判定も毎回同じだ。これはPineのドキュメント化されたマージン仕様に照らせば正しいが、この分析によれば、TradingViewのブローカーがこの一点の境界で行っていたこととは違っていた。それに合わせるには我々の側で非決定性を持ち込む必要があったので、そうはしなかった。このギャップは隠さずドキュメント化しており、252件中これが唯一の例外だった。(その後、メンテナーによる2026-09-29のベースラインではこのプローブがexcellentと判定されており、コーパスのREADMEではこの診断を見直し中としている。)
バグその1:TradingViewなら強制クローズしていたポジション
TradingViewのブローカーは、マージン要件を割り込んだポジションをいつ閉じるかを、ストラテジーのロジックの判断に委ねない。清算してしまう。レバレッジをかけたポジションやショートポジションのequityが大きく下がり、必要マージンがアカウントequityを上回ると、TradingViewは strategy.exit() の条件が満たされているかどうかに関わらず、その状態になったbarでポジションを清算する。清算価格はそのbarでポジションにとって最も不利な価格で、清算する数量はマージンの回復に必要な量の4倍(最大でポジション全体)だ。
エンジンはこれをモデル化していなかった。ポジションを保持し続け、最終的にストラテジー自身のロジックがクローズするのを待っていた。タイトなストップを持たないストラテジーでは、TradingViewなら既に清算済みのポジションが何十barも残り続けることになる。コミュニティで共有されたスクリプトのうち、レバレッジやショートのポジションを抱えたままマージンコールに至った24本では、TradingViewのトレード一覧に「Margin call」によるイグジットがあったのに、エンジンはそれを一度も出していなかった。
修正では、アカウントequityがマージン要件を割り込んだときに強制清算する処理を追加した。清算価格はTradingViewのブローカーと同じ方法で算出し、そのうえで清算数量をシンボルのlot stepに合わせて量子化する。TradingViewはシンボルのlot stepより細かい端数ロットを清算しないし、実行時にそのlot stepが渡されていれば、今は我々のエンジンもそうしない。
これが最も影響するのは、無期限先物でレバレッジやショートのストラテジーをバックテストしているユーザーだ。ドローダウン中のequityカーブが妙に滑らかに見えていたなら、これが原因だった可能性は十分にある。
バグその2:FXを考慮していなかったaffordabilityチェック
Pineストラテジーは、シンボルのquote currencyとは異なるアカウントカレンシーを宣言できる。たとえば currency.INR でUSDTペアを取引する場合だ。注文を賄えるかどうかを判定する前に、TradingViewのブローカーは注文のnotional valueをその時点のFXレートでアカウントカレンシーに変換し、同じカレンシー建てのequityと比較する。
エンジンのaffordabilityゲートはこの変換を飛ばしていた。FXレートが1:1であるかのように、USDT建てのnotionalをINR建てのequityとそのまま比較していたのだ。つまり currency.INR を宣言したストラテジーは、本来より数十倍affordableに見えていたことになる(1 USDTは数十ルピーに相当する)。影響を受けたスクリプトでは、エンジンがTradingViewのおよそ2倍のトレードを出していた。TradingViewのブローカーがunaffordableとして拒否したエントリーまで通していたのだ。
修正では、affordabilityチェックが走る前に、必要マージンへアカウントカレンシーのFXレートを適用するようにした。TradingViewが注文パイプライン上で適用するのと同じ位置だ。レートには実行時に渡された値を使う(エンジンのデフォルトは1:1)。USD建てではないアカウントカレンシーで、USDまたはUSDT建てのシンボルをバックテストしていたなら、トレード数を静かに水増ししていたのはこのバグだ。
バグその3:一度もarmされなかったトレーリングストップ
これは我々より先にユーザーに見つけてほしかったケースだ。サイレントに失敗するからだ。strategy.exit() はトレーリングストップの指定方法を2つサポートしている。エントリーからの相対距離を指定する trail_points と、絶対的なアクティベーション価格を指定する trail_price だ。
// armed correctly today; trail_price-only calls used to never arm at all
strategy.exit("TS", from_entry = "Long", trail_price = close * 1.05, trail_offset = 10)
エンジンのarming条件は trail_points しか見ていなかった。trail_price だけで指定したトレーリングイグジットは、コンパイルエラーも実行時エラーもなく通り、そして一度もarmされなかった。ポジションはトレーリングストップが有効にならないまま、バックテスト全体を通して黙って放置される。例外もログ行もなく、トレードも発生しない。ストラテジーは保護されているように見えていた。実際にはそうではなかった。
これを見つけたのは、トレーリングストップを trail_price だけで設定している、コミュニティで共有されたスクリプトで、TradingViewと一致するトレードが半分しかなかったからだ。修正後は99.7%が一致した。公開コーパスには trail_price を使うプローブが一つもなかったので、コーパスの数字にこのギャップが表れようがなかった。コーパス内訳の記事で書いたとおり、validation カテゴリはまさにこうしたギャップのためにある。収めているのは狭く合成的なストラテジーで、儲かるわけでも現実的なわけでもなく、Pineの一機能だけを単独で検証し、このような回帰が「全体の数字は問題なさそうだった」の陰に隠れないように作られている。
修正では、trail_points か trail_price のどちらかがあればトレーリングストップをarmするようにした。trail_price を使ったPineストラテジーを書いたことがあり、equityカーブがトレーリングストップなどまるで無いかのように見えて怪しいと感じたことがあるなら、そのとおりだった。この修正が初めて入ったリリースであるv0.10.14(2026-07-02)より前は、どのエンジンリリースでもトレーリングストップは存在していなかった。
これがコーパスを持つ意味そのものである理由
この3つのバグは、どれもコードレビューでは見つからなかった。適切な形のストラテジーがエンジンを通り、TradingViewと一致しない数字を出したことで見つかったのだ。167ストラテジー、そして252、さらに増え続けるコーパスを、ひと握りのスモークテストではなく、ホールドアウトされたバージョン管理コーパスとして維持している理由はそこにある。コミュニティで共有されたスクリプトを同じdiffに通しているのも同じ理由で、今回の3つが見つかったのはそちらだった。見出しの数字がきれいでも、そのストラテジーが結局いくら儲けたか、あるいは失ったかを変えてしまうバグが隠れていることがある。それを捕まえる唯一の方法は、同じground truthに対する同じdiffに、より多くの形のストラテジーを通し続けることだ。
ギャップを見つけるたびにプローブを追加し、数字も、良い結果も唯一のアノマリーも含めて、ありのまま公開し続ける。
ここから先へ
- ギャラリーを見る:現在の公開コーパスにある312ストラテジー。それぞれの個別ページで、パリティの結果とトレード件数を確認できる。
- 類似のマージンバグを見つけた経緯を読む — 別のバグ、別の境界、同じ方法。分離し、diffを取り、修正する。
- ClaudeやCursorからバックテストする:無料のホスト型MCPサーバー、または手元のOHLCVで動かすローカルのDockerサーバー。
- パリティの測り方:最新の結果とその出典。