本文へスキップ

別シンボルへの request.security 1 行:バックテストに必要なもの

読了 約6分

ある市場で売買し、別の市場でフィルターをかける戦略には、その 2 つ目の市場のバーが必要ですが、チャートの CSV には入っていません。こうしたスクリプトを動かすために PineForge のエンジンが必要とするもの、バーがないときの挙動、Docker イメージと MCP サーバーが用意するものを解説します。

2026-10-04 更新:リリース 1.1.0 以降、pineforge-release の Docker イメージは、PINEFORGE_SYMBOL_FEEDS で指定した JSON インデックスから、request.security 用の別シンボルのバーをインストールします。フィードは要求された時間足ごとに 1 本ずつ使い、集約はしません(エンジン #327、pineforge-release #23)。詳しくはエンジンの 1.1.0 の変更履歴をご覧ください。以下の本文は、2026-10-02 にリリース 1.0.1 を対象に公開したときのままです。

ある個別株を売買する戦略で、買うのは Nasdaq が上昇トレンドにあるときだけにしたいとします。TradingView ならこれは 1 行で書けて、そのまま動きます。TradingView はあらゆるシンボルのバーを持っているからです。しかし、手元の CSV で動くバックテスターが持っているのはチャートのバーだけです。そこで QQQ を求められたら、取れる道は 3 つしかありません。拒否するか、停止するか、それとも黙って間違った市場でフィルターを計算するかです。

PineForge が選ぶのは 2 つ目で、それもやむを得ないときだけです。エンジンに QQQ 自身のバーを渡せば、スクリプトは動きます。

2 つ目の市場を必要とする 1 行

次のスクリプトは、QQQ が 50 日移動平均を上回っているときだけロングします。

//@version=6
strategy("QQQ regime filter", overlay=true)
qqqUp = request.security("NASDAQ:QQQ", "D", close > ta.sma(close, 50))
fast = ta.sma(close, 20)
if ta.crossover(close, fast) and qqqUp
    strategy.entry("L", strategy.long)
if ta.crossunder(close, fast)
    strategy.close("L")

問題は 3 行目です。式 close > ta.sma(close, 50) は、QQQ 自身の日足、つまり QQQ の終値と QQQ の 50 日移動平均で評価されます。チャートのバーをいくら集約しても、これは作れません。少し前まで、PineForge はこのスクリプトを門前払いしていました。いまは、エンジンを動かす側が QQQ のバーを渡しさえすれば実行できます。

エンジンに必要なもの

エンジンの公開 C ヘッダーには、そのための呼び出しが 1 つあります(pineforge.h)。

PF_API int strategy_set_symbol_feed(pf_strategy_t s, const char* key, const char* timeframe,
                                    const pf_bar_t* bars, const int64_t* close_ms, int n);

これは 3 つの要件として読めます。key はスクリプトが渡す文字列そのもの、つまり NASDAQ:QQQ です。これで、バーがそれを要求した呼び出しに結び付けられます。timeframe は要求された時間足でなければなりません。日足の要求には日足のバーが必要で、同じシンボルでも別の時間足のフィードでは代わりになりません。そして close_ms は、各バーにそれぞれの終了時刻を与えます。「シンボルのカレンダーは推測しない」からです。バーが教えてくれない限り、エンジンには Nasdaq がいつ引けるのかが分かりません。シンボルのセッション、通貨、ティックサイズといった情報は任意で、対になる別の呼び出しで渡します。

どちらの時計で見るか

終了時刻が重要なのは、2 つの市場の引けの時刻が違うからです。エンジンはチャートの各バーで、そのバーの終了時刻までに確定した相手シンボルの最後のバーを要求に渡します(lookahead を有効にした場合は、チャートのバーの開始時刻までに始まった最後のバー)(pineforge.h)。そのため、「チャートのセッション終了より後に確定する日足は、TradingView と同じく、チャート上で 1 日遅れて見える」ことになります(MTF ガイド)。

これは TradingView のルールであって、PineForge の手抜きではありません。エンジンのテストには、ドルインデックスを要求する金の日足チャートを TradingView からエクスポートしたデータが入っています。日曜の夕方に始まる金の日足は、月曜の 17:00 ET に確定します。ドルインデックスの月曜のバーが確定するのは、その 2 時間後の 19:00 ET です。そのため TradingView では、この金のバーが読むのは金曜のドルインデックスのバーで、月曜のバーは次の金のバーで届きます(フィクスチャのノート)。相手市場のバーを、確定する前にチャートのバーへ渡してしまうバックテスターは、未来を読んでいることになります。

バーがないとき

スクリプトのトランスパイル自体は通ります。ただし、この要求が「別シンボルのバー」を読むこと、そして「何もインストールされていなければ、値を読んだ箇所で実行がエラーで止まる」ことを伝える警告が出ます(codegen)。最初に値を読んだ時点で、実行は呼び出しを名指しするメッセージとともに止まります(ソース)。

request.security("NASDAQ:QQQ", "D", ...) at line 3: no data is pinned for this request, and its value was read

決してしないのは、QQQ の代わりにチャートのバーを読むことです(変更履歴)。間違った市場で計算したフィルターは、もっともらしく見えて何の意味もないエクイティカーブを生みます。だから、止まるほうが安全な失敗なのです。止まらないケースは 2 つあります。値が一度も読まれない要求(たとえば、オフのままの入力の裏にあるもの)と、値がプロット、アラート、テーブルにしか届かない要求です。後者は na になり、「トレードには影響しない」という警告が出ます(codegen)。

Docker イメージと MCP サーバーが用意するもの

いずれも、バックテストに使うのは 1 つの市場のバー、つまりチャートのバーだけです。

  • Docker イメージは strategy.pine と ohlcv.csv 1 つを受け取り(README)、そのハーネスは別シンボルのバーをインストールしません。QQQ のスクリプトはここで上のメッセージとともに止まり、イメージは終了コード 4 で終了します。
  • ローカル MCP サーバーはこのイメージの上に作られていて、1 つの CSV でバックテストします。README にもそう書かれています。「別シンボルのバーはインストールしない……たとえば別シンボルへの request.security はエンジンではサポートされているが、ここでは、値がトレードに届きうる要求は実行を止める」(README)。
  • ホスト型 MCP サーバーはシンボル、時間足、取引所を受け取り、その 1 つの市場のバーを自分で取得します。対応しているのは暗号資産だけなので、QQQ の例のような株式の戦略はそもそも実行できません(同じ README)。

ここで多くの人がはまる落とし穴があります。Docker イメージとローカルサーバーは、チャートがどの市場なのかをエンジンに伝えません。そのため、文字列で書いたシンボルは、チャート自身の市場を指していても別シンボルとして扱われます(カバレッジのノート)。チャートのシンボルには syminfo.tickerid と書いてください。これに対する上位足の要求なら、追加のデータは要りません。エンジンがチャートのバーから上位足のバーを組み立てるからです(MTF ガイド)。ただし制約が 1 つあります。米国株や CME 先物の日中足チャートでは、TradingView の日足は取引所自身の日足で、公式の終値を使っています。これはどう集約しても再現できません(pineforge.h)。

押さえておきたいこと

  • チャート自身の市場でフィルターをかけるなら、syminfo.tickerid を使ってください。手元の CSV だけでスクリプトが動きます。
  • 別の市場でフィルターをかけるなら、エンジンを自分で動かし、実行前にその市場のバーを終了時刻つきでインストールしてください。エンジンリポジトリのテストハーネスが実例になります。スクリプトごとのリクエストマニフェストからバーをインストールしていて、マニフェストのフィールドは scripts/run_strategy.py が定義しています。
  • TradingView と比べるなら、デフォルトの lookahead オフでは、自分の市場より後に引ける市場の日足は 1 日遅れて届くものと考えてください。これは TradingView の挙動で、エンジンもそれに合わせています。

2026-10-02 に、同日リリースの PineForge エンジンと codegen 1.0.1 で確認しました。この挙動が最初に入ったのは 1.0.0 です。

次に読むなら