Pineスクリプト作成代行はこちら

Pineスクリプトで勝てるストラテジーを作る方法|検証の実際

「バックテストで勝率70%」。この数字だけを見て安心していないだろうか。

Pineスクリプトのストラテジー機能を使えば、自分のトレード手法を過去データで検証できる。strategy()で宣言し、strategy.entry()でエントリーし、ストラテジーテスターで成績を確認する。手順自体は難しくない。

問題は「その数字が信頼できるかどうか」だ。

バックテストの勝率70%は、パラメータを過去データに合わせて調整した結果かもしれない。スプレッドやスリッページを考慮していないかもしれない。特定の相場環境でたまたまハマっただけかもしれない。こうした落とし穴を知らずにリアル運用に移ると、「バックテストでは勝てたのに実際は負ける」という典型的な失敗パターンにはまる。

この記事では、Pineスクリプトでストラテジーを作成する方法を、「コードの書き方」だけでなく「検証結果の読み方」「数字の信頼性の判断方法」まで含めて解説する。コピペで動くサンプルコードを複数掲載しているので、実際にTradingViewで動かしながら読んでほしい。

Pineスクリプトの実践テクニックを見る

ストラテジーの基本構造

Pineスクリプトのストラテジーは、indicator()の代わりにstrategy()で宣言する。これだけで、TradingViewのストラテジーテスターが有効になり、バックテスト結果を確認できるようになる。

最小構成のストラテジーはこうなる。

//@version=6
strategy("最小ストラテジー", overlay=true)

// エントリー条件
if ta.crossover(ta.ema(close, 12), ta.ema(close, 26))
    strategy.entry("Long", strategy.long)

// エグジット条件
if ta.crossunder(ta.ema(close, 12), ta.ema(close, 26))
    strategy.close("Long")

たった8行。12EMAが26EMAを上抜けたらロング、下抜けたらクローズ。これをチャートに追加すると、過去データ全体で売買シミュレーションが実行され、ストラテジーテスターに成績が表示される。

ストラテジーのコードは、大きく4つのパートで構成される。

①宣言部: strategy()で名前、初期資金、ポジションサイズ、手数料などを設定する。

②計算部: テクニカル指標の計算、条件判定、フィルターの適用を行う。

③注文部: strategy.entry()でエントリー、strategy.close()でクローズ、strategy.exit()でTP/SLを設定する。

④描画部: plot()plotshape()でインジケーターやシグナルを可視化する。

strategy()の設定 — ここで結果が大きく変わる

strategy()の引数は、バックテスト結果に直接影響する。適当に設定すると、結果の信頼性が大きく下がる。

//@version=6
strategy("EMAクロス", 
  overlay=true,
  initial_capital=1000000,       // 初期資金100万円
  default_qty_type=strategy.percent_of_equity,
  default_qty_value=10,          // 資金の10%でエントリー
  commission_type=strategy.commission.percent,
  commission_value=0.1,          // 往復0.1%の手数料
  slippage=2,                    // 2ティックのスリッページ
  process_orders_on_close=false, // バーの終値で注文を処理しない
  calc_on_every_tick=false)      // ティックごとの計算を無効化

特に重要な設定を説明する。

initial_capital(初期資金)。 デフォルトは$1,000,000だが、自分の実際のトレード資金に近い値を設定する。資金量によってポジションサイズが変わり、成績も変わる。

commission_value(手数料)。 デフォルトは0(手数料なし)。手数料なしのバックテストは現実離れしている。FXなら0.01〜0.05%、暗号資産なら0.05〜0.1%が目安。スプレッドを含めたい場合は、手数料をやや大きめに設定するか、slippageで調整する。

slippage(スリッページ)。 実際の約定では、指定価格と約定価格にずれが生じる。1〜3ティックを設定しておくと現実に近い結果が得られる。

process_orders_on_close。 trueにすると、バーの終値確定時点で注文が処理される。false(デフォルト)だと次のバーの始値で約定する。trueは「未来の情報を使っている」ことになりやすいので、基本はfalseのままにする。

エントリーとエグジットの書き方

ストラテジーの注文関数は3つある。

strategy.entry() — 新規エントリー。同じIDで複数回呼ぶと、最新の注文で上書きされる。

strategy.close() — 指定IDのポジションを成行でクローズ。

strategy.exit() — TP(利確)/SL(損切)/トレーリングストップを設定。エントリー後に自動で機能する。

この3つの使い分けを、実践的なストラテジーで見てみよう。

サンプル①: EMAクロス+RSIフィルター+固定TP/SL

最も基本的で応用しやすい構成。トレンドフォロー系のストラテジーだ。

//@version=6
strategy("EMAクロス+RSI", overlay=true,
  initial_capital=1000000,
  default_qty_type=strategy.percent_of_equity,
  default_qty_value=10,
  commission_value=0.05,
  slippage=2)

// === パラメータ ===
emaFast   = input.int(12, "短期EMA", minval=1, group="EMA設定")
emaSlow   = input.int(26, "長期EMA", minval=1, group="EMA設定")
rsiLen    = input.int(14, "RSI期間", minval=1, group="フィルター")
rsiUpper  = input.int(70, "RSI上限", group="フィルター")
rsiLower  = input.int(30, "RSI下限", group="フィルター")
tpPips    = input.float(100, "利確(pips)", group="決済")
slPips    = input.float(50, "損切(pips)", group="決済")

// === 計算 ===
fast = ta.ema(close, emaFast)
slow = ta.ema(close, emaSlow)
rsi  = ta.rsi(close, rsiLen)

// === pips → 価格変換 ===
pipValue = syminfo.mintick * 10
tp = tpPips * pipValue
sl = slPips * pipValue

// === エントリー条件 ===
longCond  = ta.crossover(fast, slow) and rsi < rsiUpper
shortCond = ta.crossunder(fast, slow) and rsi > rsiLower

// === 注文 ===
if longCond
    strategy.entry("Long", strategy.long)
    strategy.exit("Long TP/SL", "Long", profit=tp/syminfo.mintick, loss=sl/syminfo.mintick)

if shortCond
    strategy.entry("Short", strategy.short)
    strategy.exit("Short TP/SL", "Short", profit=tp/syminfo.mintick, loss=sl/syminfo.mintick)

// === 描画 ===
plot(fast, "短期EMA", color.blue)
plot(slow, "長期EMA", color.red)

このストラテジーのポイントを解説する。

RSIフィルター。 EMAクロスだけでは、レンジ相場でダマシのシグナルが乱発する。RSIが買われすぎ圏(70超)でのロングを除外し、売られすぎ圏(30未満)でのショートを除外することで、無駄なエントリーを減らす。

strategy.exit()のprofit/loss。 profitlossは「ティック数」で指定する。pips単位で入力したい場合は、上記のようにpipValueで変換する必要がある。FXの場合、1pip = 10 mintick(5桁ブローカー)が一般的。

ロングとショートの両方向。 トレンドフォロー系は、売りと買いの両方向をテストすることで、ストラテジーの堅牢性を確認できる。片方向だけの検証は、相場の方向性にバイアスがかかる。

サンプル②: ブレイクアウト+ATRベースTP/SL

固定pipsのTP/SLは、ボラティリティが変動する相場では機能しにくい。ATR(Average True Range)でTP/SLを動的に調整するストラテジーを紹介する。

//@version=6
strategy("ATRブレイクアウト", overlay=true,
  initial_capital=1000000,
  default_qty_type=strategy.percent_of_equity,
  default_qty_value=10,
  commission_value=0.05,
  slippage=2)

// === パラメータ ===
lookback  = input.int(20, "ブレイクアウト期間", minval=5, group="エントリー")
atrLen    = input.int(14, "ATR期間", minval=1, group="ATR")
atrTPmul  = input.float(2.0, "ATR × TP倍率", step=0.1, group="決済")
atrSLmul  = input.float(1.0, "ATR × SL倍率", step=0.1, group="決済")

// === 計算 ===
highestHigh = ta.highest(high, lookback)
lowestLow   = ta.lowest(low, lookback)
atr         = ta.atr(atrLen)

// === エントリー条件 ===
longBreak  = close > highestHigh[1]
shortBreak = close < lowestLow[1]

// === 注文 ===
if longBreak
    strategy.entry("Long", strategy.long)
    strategy.exit("Long Exit", "Long",
      profit=atr * atrTPmul / syminfo.mintick,
      loss=atr * atrSLmul / syminfo.mintick)

if shortBreak
    strategy.entry("Short", strategy.short)
    strategy.exit("Short Exit", "Short",
      profit=atr * atrTPmul / syminfo.mintick,
      loss=atr * atrSLmul / syminfo.mintick)

// === 描画 ===
plot(highestHigh, "高値ライン", color.green, style=plot.style_stepline)
plot(lowestLow, "安値ライン", color.red, style=plot.style_stepline)

ATRベースの利点。 ボラティリティが高い相場ではTP/SLが広がり、低い相場では狭まる。相場環境に自動適応するため、固定pipsよりも堅牢性が高い。

ATR倍率の目安。 TP=ATR×2倍、SL=ATR×1倍が一般的な起点。リスクリワード比2:1になる。ここからパラメータを調整していく。

サンプル③: 時間帯フィルター付きストラテジー

FXやゴールドは、東京・ロンドン・ニューヨークの各セッションで値動きの性質が異なる。時間帯フィルターを入れることで、有効な時間帯だけにエントリーを限定できる。

//@version=6
strategy("セッション限定EMAクロス", overlay=true,
  initial_capital=1000000,
  default_qty_type=strategy.percent_of_equity,
  default_qty_value=10,
  commission_value=0.05,
  slippage=2)

// === パラメータ ===
emaFast   = input.int(12, "短期EMA", group="EMA設定")
emaSlow   = input.int(26, "長期EMA", group="EMA設定")
useSession = input.bool(true, "セッション限定", group="時間帯")
sessionStr = input.session("0800-1600", "許可時間帯(UTC)", group="時間帯")

// === 計算 ===
fast = ta.ema(close, emaFast)
slow = ta.ema(close, emaSlow)

// === 時間帯フィルター ===
inSession = useSession ? not na(time(timeframe.period, sessionStr)) : true

// === エントリー条件 ===
longCond  = ta.crossover(fast, slow) and inSession
shortCond = ta.crossunder(fast, slow) and inSession

// === 注文 ===
if longCond
    strategy.entry("Long", strategy.long)
if shortCond
    strategy.entry("Short", strategy.short)

// セッション外で全ポジションクローズ
if useSession and not inSession
    strategy.close_all("セッション終了")

// === 描画 ===
plot(fast, "短期EMA", color.blue)
plot(slow, "長期EMA", color.red)
bgcolor(inSession ? color.new(color.blue, 95) : na)

input.session()。 時間帯を「HHMM-HHMM」形式で設定パネルから指定できる。UTC基準なので、東京時間(JST)の場合は-9時間で計算する。ロンドン時間08:00-16:00(UTC)が「0800-1600」。

strategy.close_all()。 セッション外に入ったタイミングで全ポジションをクローズする。ポジションを持ち越さないデイトレードスタイルのテストに使う。

bgcolor()で時間帯を可視化。 許可されている時間帯の背景色を薄く塗ることで、エントリーが制限されている時間帯を目視で確認できる。

ストラテジーテスターの読み方

ストラテジーをチャートに追加すると、画面下部の「ストラテジーテスター」タブに結果が表示される。数字の意味を正しく理解しないと、判断を誤る。

純利益(Net Profit)。 全トレードの損益合計。プラスならトータルで勝ち、マイナスなら負け。ただし、この数字だけでは判断できない。初期資金に対する割合(%)で見ることが重要。

勝率(Percent Profitable)。 勝ちトレード数 ÷ 全トレード数。勝率50%でもプロフィットファクターが高ければ収益性はある。勝率だけに注目しないこと。

プロフィットファクター(Profit Factor)。 総利益 ÷ 総損失。1.0なら損益トントン、1.5以上なら優秀、2.0以上はかなり良い。ただし、トレード数が少ないと偶然で2.0以上になることもある。

最大ドローダウン(Max Drawdown)。 資産がピークからどれだけ落ち込んだかの最大値。これが許容範囲を超えるストラテジーは、リアル運用で精神的に耐えられない。一般的に資金の20%以内が目安。

トレード数(Total Trades)。 バックテストの信頼性を左右する。30回未満のトレードでは統計的に有意ではない。最低100回以上のトレード数を目指す。

平均トレード(Avg Trade)。 1回のトレードあたりの平均損益。これが手数料+スリッページより大きくなければ、実運用では負ける。

Pineスクリプトの基礎をもっと詳しく知りたい方はこちら。

Pineスクリプト(Pine Script)とは?できること・始め方を完全解説

Pineスクリプトの実践テクニックを見る

バックテストの落とし穴 — 数字を鵜呑みにしない

バックテストの数字が良くても、リアル運用で勝てるとは限らない。よくある落とし穴を知っておこう。

カーブフィッティング(過剰最適化)。 パラメータを微調整して、過去データに完璧にフィットさせること。EMA期間を12→13に変えたら利益が20%増えた。さらに14にしたら25%。こうやって過去データに最適化したパラメータは、将来の相場では機能しない。

対策は「パラメータの安定領域を探す」こと。EMA期間10〜16のどれでもプロフィットファクターが1.3以上あるなら、そのストラテジーは堅牢だ。特定の1つの値でだけ良い結果が出るなら、それはカーブフィッティングの可能性が高い。

手数料・スリッページの無視。 デフォルト設定(手数料0、スリッページ0)でバックテストすると、現実よりもかなり良い数字が出る。特にスキャルピング系のストラテジーは、手数料を入れた途端に収益がマイナスに転じることがある。

バーの終値ベースの約定。 TradingViewのストラテジーテスターは、基本的にバーの終値(または次のバーの始値)で約定を計算する。バー内の値動き(ヒゲ部分)は考慮されない。リアルタイムでは、バーの途中で条件が成立してエントリーするため、バックテスト通りの価格で約定しないことがある。

生存者バイアス。 上場廃止になった銘柄はTradingViewのチャートから消えている。生き残った銘柄だけでバックテストすると、結果が良く見える傾向がある。

堅牢性を確かめる方法

カーブフィッティングを避け、リアル運用でも通用するストラテジーかどうかを確認する方法。

複数銘柄テスト。 USDJPY、EURUSD、GBPUSD、XAUUSD(ゴールド)など、複数の銘柄で同じストラテジーを走らせる。1つの銘柄でしか機能しないストラテジーは汎用性が低い。

複数時間足テスト。 15分足、1時間足、4時間足、日足で同じストラテジーをテストする。特定の時間足でだけ良い結果が出る場合は、その時間足のデータ特性に依存している可能性がある。

分割テスト(イン/アウトサンプル)。 過去データを前半と後半に分ける。前半でパラメータを決め、後半でそのパラメータのまま検証する。前半は良いが後半は悪い、というパターンならカーブフィッティングの兆候だ。

Pineスクリプトでは、time関数を使ってテスト期間を分割できる。

//@version=6
strategy("分割テスト", overlay=true)

// テスト期間の分割
cutoff = timestamp(2024, 1, 1)
isInSample  = time < cutoff   // 2024年以前 = パラメータ調整用
isOutSample = time >= cutoff   // 2024年以降 = 検証用

// 背景色でイン/アウトサンプルを可視化
bgcolor(isInSample ? color.new(color.blue, 95) : color.new(color.green, 95))

パラメータ感度分析。 EMA期間を10→12→14→16と変えたとき、プロフィットファクターが1.5→1.8→0.5→1.6と不安定に変動するなら、そのストラテジーはパラメータに敏感すぎる。1.3→1.4→1.5→1.4のように安定的に推移するなら、堅牢性が高い。

strategy.exit()の応用 — 決済ロジックを磨く

多くの初心者は、エントリーロジックに時間をかけすぎて、決済ロジックを軽視する。実際には、「いつ出るか」の方が収益に大きく影響する。

トレーリングストップ。 利益が伸びたら損切りラインを切り上げる。

strategy.exit("Trail", "Long",
  trail_points=50,      // 50ティック利益が出たらトレーリング開始
  trail_offset=20)      // 高値から20ティック逆行で決済

TP/SLとトレーリングの併用。 利確ラインに達する前にトレーリングで利益を確保する。

strategy.exit("複合決済", "Long",
  profit=200,           // 200ティックで利確
  loss=100,             // 100ティックで損切
  trail_points=80,      // 80ティック利益でトレーリング開始
  trail_offset=30)      // 高値から30ティック逆行で決済

時間ベースの決済。 一定時間(バー数)が経過してもTP/SLに到達しない場合、ポジションを閉じる。含み損が膨らむ前に撤退するロジック。

//@version=6
strategy("時間決済", overlay=true)

var int entryBar = 0

if ta.crossover(ta.ema(close, 12), ta.ema(close, 26))
    strategy.entry("Long", strategy.long)
    entryBar := bar_index

// エントリーから10バー経過で強制クローズ
if strategy.position_size > 0 and bar_index - entryBar >= 10
    strategy.close("Long", comment="時間切れ")

ストラテジーからインジケーターへの変換

バックテストで有効性を確認したストラテジーは、実際のトレードではインジケーターとして使う。strategy()indicator()に変え、strategy.entry()plotshape()に、strategy.exit()alertcondition()に置き換える。

//@version=6
// strategy("EMAクロス", overlay=true)  // ← コメントアウト
indicator("EMAクロス シグナル", overlay=true)

fast = ta.ema(close, 12)
slow = ta.ema(close, 26)

longCond  = ta.crossover(fast, slow)
shortCond = ta.crossunder(fast, slow)

// strategy.entry() の代わりにシグナル表示
plotshape(longCond, "ロング", shape.triangleup, location.belowbar, color.green, size=size.small)
plotshape(shortCond, "ショート", shape.triangledown, location.abovebar, color.red, size=size.small)

// アラート条件(スマホ通知用)
alertcondition(longCond, "ロングシグナル", "EMAゴールデンクロス")
alertcondition(shortCond, "ショートシグナル", "EMAデッドクロス")

plot(fast, "短期EMA", color.blue)
plot(slow, "長期EMA", color.red)

ストラテジーは「過去データでの検証」、インジケーターは「リアルタイムでのシグナル表示とアラート」。この2つをセットで作ることで、検証済みの手法をリアルトレードに活かせる。

よくある質問

Q: ストラテジーとインジケーターの違いは?

ストラテジーはstrategy()で宣言し、仮想の売買を実行してバックテスト結果を表示する。インジケーターはindicator()で宣言し、チャート上に描画する。ストラテジーは検証用、インジケーターはリアルトレード用。

Q: ストラテジーで自動売買はできる?

Pineスクリプト単体では取引所に注文を送れない。ただし、alertcondition()やアラートのWebhook機能を使えば、外部の自動売買システムと連携できる。

Q: バックテスト期間はどのくらい必要?

最低でも100トレード以上が出る期間。日足なら2〜5年、1時間足なら半年〜1年、15分足なら3〜6ヶ月が目安。短すぎると統計的に信頼できない。

Q: 勝率とプロフィットファクター、どちらが重要?

プロフィットファクター。勝率40%でもプロフィットファクター2.0なら十分に利益が出る(損小利大型)。逆に勝率80%でもプロフィットファクターが1.0以下なら長期的に負ける(コツコツドカン型)。

Q: process_orders_on_closeはtrueにすべき?

基本はfalse(デフォルト)のままにする。trueにすると「バーの終値を見てからそのバーの終値で約定する」という、リアルでは不可能な動作になり、バックテスト結果が過大評価される。

まとめ

Pineスクリプトのストラテジーは「コードを書いてバックテストする」までは簡単だ。難しいのは「その結果が信頼できるかを判断する」こと。

手数料とスリッページは必ず設定する。カーブフィッティングを避けるため、パラメータの安定領域を探す。複数銘柄・時間足でテストして堅牢性を確認する。分割テストでイン/アウトサンプルの成績を比較する。

バックテストの数字が良いことと、リアルで勝てることは別の話だ。この記事で紹介した検証手法を使って、「本当に使えるストラテジー」を見極めてほしい。

Pineスクリプトの実践ノウハウをもっと見る

※当サイトの内容は投資助言を目的としたものではありません。FX取引にはリスクが伴い、投資元本を失う可能性があります。投資判断はご自身の責任でお願いいたします。