本文へスキップ

2つのPineエンジンが食い違うとき——PineForgeとPyneCoreのクロス検証

読了 約9分

PyneCoreをTradingViewと並ぶ第二ソースのエンジンとしてどう使っているか、2026年5月のスイープで何が見えたか、そして友好的な第二のエンジンがなぜ私たちにとって最も役立つデバッグ道具なのか。

この記事の数値は2026年5月5日に計測したもので、当時のまま掲載しています。最新の結果はパリティの測定方法をご覧ください。

2026年5月の時点で、TradingView の外で Pine 戦略をバックテストできるエンジンとして私たちが把握していたのは二つでした。私たちのものと、PyneCore です。PyneSys の PyneComp が Pine を Python にトランスパイルし、オープンソースの Python ランタイムである PyneCore がその結果を実行します。言語もランタイムも違いますが、元になる素材は同じで、守るべき API 定義も同じです。

私たちは PyneCore を第二ソースのオラクルとして扱っています。2026年5月には、TradingView の「List of Trades」エクスポートと PyneCore の実行結果の両方に対して、パリティ・スイープを実行しました。ある戦略のトレードについて三者が一致すれば、その結果を信じます。二者が一致して一者だけが外れたら、まず調べるのは外れた側です。

この記事では、そのスイープの数字がどうだったかを紹介します。現在の比較、つまりエンジンリポジトリで 2026-09-29 に実施した比較ベンチマーク(計測に使ったエンジンは 35db01c8 で、v1.0.0 にはこのコミットが含まれています)は、比較ページに掲載しています。

コーパス

公開スイープに含まれていた参照戦略は 50 本です(より大きな 162 本のコーパスは当時非公開で、トレード比較レポート、つまりエンジンのコミット 7e526c1a 時点の benchmarks/results/trade_comparison.md で公開していたのはこのサブセットでした)。各戦略には次の処理を行いました。

  • PyneComp で Python にコンパイルし、カノニカル OHLCV 上で PyneCore が実行
  • PineForge の codegen で C++ にコンパイルし、同じ OHLCV 上で pineforge-engine が実行
  • 両方の結果を TradingView の CSV エクスポートと diff。比較ウィンドウは [OHLCV span] ∩ [TV entry span] ∩ [engine entry span] に切り詰める
  • 5 段階の尺度(excellent / strong / moderate / weak / minimal)で一致度を判定

主な結果

スイープ対象の 50 本のうち、47 本は PineForge と PyneCore で同じティアになりました。両エンジンとも excellent だったのが 45 本、strong だったのが 2 本です。この 2 本(13-parabolic-asr と 17-bos-curv)では両エンジンがまったく同じトレードを出しており、トレード数はどちらも TV と 1.8〜2.8% 異なっていました。当時の基準で excellent とは、トレード数の差が 1% 未満、エントリーとエグジットの p90 が 0.01% 未満、PnL の p90 が 1% 未満(trail_* 系のエグジットでは、エグジットと PnL の上限を緩和)で、かつ比較ウィンドウ内にある TV のトレードの 95% 以上が一致していることを指していました。

ティアが食い違った 3 本は、興味深いケースです。3 本とも PineForge のほうが上でした。

戦略PineForgePyneCoreメモ
49-partial-exit-qty-percentexcellentweakPyneCore がエグジットの約定を過剰に出す
06-liquidity-sweepexcellentmoderatePyne 側でエグジットのタイミングがずれる
07-scalping-strategyexcellentmoderatePyne 側で PnL p90 が高い。エントリーは一致

いちばん興味深いケース

49-partial-exit-qty-percent は、差が最も大きかった戦略です。この戦略は strategy.exit(qty_percent=...) で利確時にポジションの一部を切り離し、全決済、再エントリー、再び部分決済というサイクルを繰り返します。テストウィンドウ全体での結果は次のとおりです。

TV trades (raw, in-window):  725
PineForge engine trades:     852  →  725 in-window  →  725 matched (100% of TV)
PyneCore  engine trades:    3297  →  2805 in-window →  582 matched (80.3% of TV)

この戦略での PyneCore の件数差分は 74% です。ハーネスは件数の差を大きいほうの件数で割るので、ここではウィンドウ内の 2,805 件と TV の 725 件の差を、2,805 件で割った値になります。PineForge は 0% です。p90 の指標を見ると、次のようになります。

PineForge: count_delta 0.0000% · entry p90 0.0000% · exit p90 0.0004% · PnL p90 0.1321%
PyneCore : count_delta 74.1533% · entry p90 0.0000% · exit p90 1.0376% · PnL p90 (high)

つまり、TV のトレードとペアになったものについては、両エンジンとも TV と同じ価格でエントリーしています(entry p90 はどちらも 0%)。ただし、PyneCore がペアにできたのは TV の 725 件のうち 582 件だけです。食い違っているのはエグジットです。PyneCore は同じ戦略に対して、TradingView のおよそ 4 倍のトレードを生成しています(ウィンドウ内で 2,805 件対 725 件)。おそらく、TV のオーダー処理が完了とみなす前に同じ qty_percent 句が再びアームされ、部分決済のサイクルが過剰に発火しているのでしょう。

これは PyneCore の品質を評価する話ではありません。Pine の strategy.exit() のセマンティクスにおけるコーナーケースです。比較はどちらの方向にも働きます。PyneCore が TV と一致しているのに PineForge だけが外れていれば、バグは私たちの側にあります。

残りの 2 本

06-liquidity-sweep と 07-scalping-strategy は、同じ形の差がより小さく出たケースです。どちらもエントリーの一致は 100% です。エグジット価格の p90 のずれは、PyneCore が 1.65%(06)と 0.48%(07)なのに対し、PineForge は 0.0005% と 0.029% です。トレール型のエグジットや部分約定のセマンティクスは、Pine のリファレンス実装のなかでも最もドキュメントが乏しい部分で、どちらのエンジンも、観測できるトレードの挙動からリバースエンジニアリングするしかありません。

PyneCore のコンパイル API を呼び出す

PyneSys はコンパイル処理を、api.pynesys.io で HTTP 経由で公開しています。curl で直接呼び出せます。

curl -X POST https://api.pynesys.io/compiler/compile \
  -H "Authorization: Bearer $PYNESYS_API_KEY" \
  --data-urlencode 'script=//@version=6
strategy("ma cross", overlay=true)
if ta.crossover(close, ta.sma(close, 20))
    strategy.entry("L", strategy.long)'

レスポンスは、オープンソースの pynecore ランタイム向けの Python ファイルです。以下は 2026年5月時点の出力です。新しいリリースの PyneComp は、ヘッダーに自身のバージョンを書き込み、ファイルには @pyne edge と記します。

"""
@pyne
 
This code was compiled by PyneComp v6.0.31 — the Pine Script to Python compiler.
Run with open-source PyneCore: https://pynecore.org
Compile Pine Scripts online at PyneSys: https://pynesys.io
"""
from pynecore.lib import close, script, strategy, ta
 
 
@script.strategy("ma cross", overlay=True)
def main():
    if ta.crossover(close, ta.sma(close, 20)):
        strategy.entry('L', strategy.long)
 
 
if __name__ == "__main__":
    from pynecore.standalone import run
    run(__file__)

これを Python ファイルに保存して pip install pynesys-pynecore を実行し、OHLCV を指定すれば、PineForge と diff を取るための第二エンジンの出力が得られます。

なぜこれが重要なのか

1 つのバックテストエンジンが「この戦略のリターンはテスト期間で 12.4%」と報告しても、そこから分かるのはただ一つ、そのエンジンがそう主張しているということだけです。比較対象がなければ、エンジンの言うことをそのまま信じるしかありません。

独立した 2 つのエンジンが、同じスクリプトで同じ結果を出すなら、はるかに強い証拠になります。食い違った場合は、その食い違い自体が仮説になります。どちらかにバグがあるのか、Pine のセマンティクスのコーナーケースが曖昧なのか、あるいはテストデータに、どちらのエンジンも扱えないエッジ条件があるのか。どれも知っておく価値があります。

私たちは PyneCore をこのように使っています。現在、エンジンリポジトリのベンチマークは、同じ 200 本の戦略で PineForge と PyneCore の両方を TradingView と照らして評価しています(PineForge はさらに 1 本多く実行していますが、その戦略のソースは PyneSys のコンパイラが受け付けません)。実際のお金を投じるつもりの戦略をリリースするなら、あなたも第二のエンジンを使うべきです。

この記事で扱っていないこと

  • インジケーター単位のずれ。 別のレポートで、PineForge、PyneCore、PineTS のインジケーター値を個別に比較しています。比較はエンジン同士で、TradingView とは比べていません。レポートはエンジンリポジトリの benchmarks/results/indicator_comparison.md にあります。
  • 当時の内部コーパスにあり、公開スイープには含まれていなかった残りの約 110 本の戦略。 これらは Pine のコーナーケース(UDT メソッド、マルチタイムフレーム参照、OCA エグジットグループ)を検証するものでした。その手法は、その後検証方法のページで公開しています。

クロス検証を自分で試す

2 つのエンジン、1 つのソース、比べる出力は 3 つ。すべて一致すれば、リリースしてかまいません。食い違えば、実際のお金を賭ける前に有益なことが分かったことになります。