PR

Pine Script arrayの使い方【直近N本を管理】

Pineスクリプト

この記事でわかること

  • 配列が必要な場面と、close[1]で足りる場面の見分け方
  • 直近N件だけを保持するリングバッファの実装コード
  • 直近20回のシグナル勝率を自動集計するコード

「直近5回のシグナルが出た価格を覚えておいて、その平均を線で引きたい」。Pine Scriptを書き慣れてくると、こういう要望が出てきます。ところが変数に代入しても、次のシグナルで上書きされて消えてしまいます。

複数の値をまとめて持ち回るには配列(array)が必要です。この記事では、配列を使うべき場面の判断基準と、そのまま貼って動く実装コードを2本紹介します。

📊 この記事はTradingViewユーザー向けです

TradingViewをまだ使っていない方は、まず無料アカウントを作成してみましょう。登録は30秒、クレジットカード不要です。

無料でTradingViewを始める →

配列が必要になるのはどんなときか

最初に大事な話をします。Pine Scriptでは、配列を使わなくても解ける問題がかなり多いです。ここを間違えると、コードが無駄に複雑化します。

配列がいらないケース

Pine Scriptには履歴参照演算子[]があります。close[1]は1本前の終値、close[10]は10本前の終値です。過去の値を「決まった本数だけさかのぼって見る」なら、これで足ります。

やりたいこと 配列を使わない書き方
直近20本の最高値 ta.highest(high, 20)
3本前の終値 close[3]
直近10本の平均 ta.sma(close, 10)

これらを配列で書き直しても、コードが長くなるだけで速くも正確にもなりません。

配列でないと解けないケース

一方、次のような要件は[]では書けません。共通点は「何本前か」が事前に決まっていないことです。

  • 直近5回シグナルが出たときの価格(シグナルの間隔は毎回バラバラ)
  • 直近20回のトレード結果から勝率を出す
  • 条件を満たした価格帯だけを集めて、あとから並べ替える

「10本前」は[]で取れますが、「5回前のシグナル」は取れません。ここが配列の出番です。

最低限おぼえる5つの操作

配列の関数は数十ありますが、実務で使うのはほぼこの5つです。

関数 役割
array.new_float(0) 空の配列を作る(0は初期サイズ)
array.push(a, v) 末尾に追加する
array.shift(a) 先頭(最も古い)を取り出して削除する
array.get(a, i) i番目を読む(0始まり)
array.size(a) 要素数を返す

この5つに、集計用のarray.sum()array.avg()を足せば、たいていの処理は書けます。なお配列の最大要素数は100,000個です。実運用では上限に当たる前に、後述のリングバッファで件数を絞るのが定石です。

実践1:直近N件のシグナル価格を保持する

配列の使い方として最も出番が多いのがリングバッファです。新しい値を末尾に足し、上限を超えたら先頭を捨てる。これだけで「直近N件」が常に維持されます。

//@version=5
indicator("直近Nシグナルの平均価格", overlay=true)

keepN  = input.int(5,  "保持するシグナル数", minval=1, maxval=50)
rsiLen = input.int(14, "RSI期間", minval=2)
osLv   = input.int(30, "売られすぎライン")

r   = ta.rsi(close, rsiLen)
sig = ta.crossover(r, osLv)

// var を付ける。付けないと毎バー空の配列に戻る
var float[] prices = array.new_float(0)

if sig
    array.push(prices, close)          // 末尾に追加
    if array.size(prices) > keepN
        array.shift(prices)            // 上限超過なら最古を捨てる

// size が 0 のときに avg を呼ぶとエラーになるので必ず確認する
avgPrice = array.size(prices) > 0 ? array.avg(prices) : na

plot(avgPrice, "直近シグナル平均", color=color.orange,
     style=plot.style_stepline, linewidth=2)
plotshape(sig, title="シグナル", style=shape.triangleup,
          location=location.belowbar, color=color.green, size=size.tiny)

コードのポイント

  • varが生命線varを付けた変数はバーをまたいで値が残ります。付け忘れると毎バーarray.new_float(0)が実行され、配列は常に空のままです
  • push→上限チェック→shiftの順:先に追加してから溢れた分を捨てます。逆順にすると常に1件少なくなります
  • array.avg()の前にsize確認:要素0の配列に集計関数を使うとエラーで停止します

varの挙動そのものはPine Script v5の基礎文法まとめで解説しています。実際に1本ずつ検証しながら値の変化を追いたいなら、バーリプレイ検証の手順で紹介した記録ヘルパーと組み合わせると挙動がつかみやすいです。

TradingViewで実際に試してみよう

この記事の内容はTradingViewで今すぐ実践できます。無料プランでも十分対応できます。

▶ TradingViewを無料で開く

クレジットカード不要・いつでも解約OK

実践2:直近20回のシグナル勝率を出す

もう一歩踏み込みます。シグナルが出たあと5本先の終値と比べて勝ち負けを判定し、直近20回分の勝率を表示するコードです。「判定待ち」と「判定済み」を別々の配列で管理するのがポイントです。

//@version=5
indicator("シグナル勝率トラッカー", overlay=true)

aheadN = input.int(5,  "判定するバー数", minval=1)
keepN  = input.int(20, "集計する件数",   minval=5)

sig = ta.crossover(ta.rsi(close, 14), 30)

var int[]   waitBar = array.new_int(0)     // 判定待ちシグナルのbar_index
var float[] waitPx  = array.new_float(0)   // そのときの終値
var int[]   results = array.new_int(0)     // 1=勝ち / 0=負け

if sig
    array.push(waitBar, bar_index)
    array.push(waitPx,  close)

// 判定待ちを後ろから走査する(削除するので逆順が安全)
if array.size(waitBar) > 0
    for i = array.size(waitBar) - 1 to 0
        if bar_index - array.get(waitBar, i) >= aheadN
            win = close > array.get(waitPx, i) ? 1 : 0
            array.push(results, win)
            array.remove(waitBar, i)
            array.remove(waitPx,  i)

// 直近 keepN 件だけ残す
while array.size(results) > keepN
    array.shift(results)

n       = array.size(results)
winRate = n > 0 ? 100.0 * array.sum(results) / n : na

var table t = table.new(position.top_right, 2, 3, border_width=1)
if barstate.islast
    table.cell(t, 0, 0, "集計件数")
    table.cell(t, 1, 0, str.tostring(n) + " / " + str.tostring(keepN))
    table.cell(t, 0, 1, "勝率")
    table.cell(t, 1, 1, na(winRate) ? "—" : str.tostring(winRate, "#.#") + "%")
    table.cell(t, 0, 2, "判定待ち")
    table.cell(t, 1, 2, str.tostring(array.size(waitBar)) + "件")

コードのポイント

  • 削除するループは必ず後ろからfor i = array.size(waitBar) - 1 to 0と降順で回します。前から回すと削除で添字がずれ、判定漏れが起きます
  • 2つの配列を同じ添字で対応させるwaitBarwaitPxは必ず同時にpushし、同時にremoveします。片方だけ操作すると対応が崩れます
  • 100.0を先に掛ける:整数どうしの割り算は小数が落ちます。先に浮動小数を掛けて計算精度を確保します

この勝率はあくまで「シグナルの傾向」であり、損切りや手数料を含んだ成績ではありません。実際の期待値を測るならバックテストを実行する方法に進んでください。

ハマりどころ3つ

1. varの付け忘れ

最も多いバグです。varがないと配列は毎バー作り直され、要素数は常に0か1のままです。「配列に入れているはずなのに増えない」と感じたら、まずここを疑ってください。

2. 範囲外アクセス

要素が3個の配列にarray.get(a, 5)を実行すると、Array index out of boundsでスクリプトが止まります。読む前に必ずarray.size()で件数を確認するのが鉄則です。とくに起動直後は要素が溜まっていません。

3. 配列は参照型である

ここは変数の感覚で書くと事故ります。b = aと書いても配列は複製されず、2つの名前が同じ配列を指すだけです。bを変更するとaも変わります。中身をコピーしたい場合はarray.copy(a)を使ってください。

こんな場合は配列を使わない

  • 固定本数の過去を見るだけclose[5]ta.highest(high, 20)で足ります。配列にする意味がありません
  • 値を1つだけ持ち回りたいvar float lastPrice = naのような通常のvar変数で十分です
  • 毎バー全要素を走査する処理:件数が増えるほど計算量が膨らみます。ループが必要か、集計関数で代替できないかを先に検討してください

まとめ

  • 「何本前か」が決まっていない値を集めるときだけ配列を使う。決まっているなら[]で足りる
  • 覚えるのはnewpushshiftgetsizeの5つでほぼ足りる
  • push→上限チェック→shiftのリングバッファで「直近N件」を維持する
  • 削除を伴うループは後ろから回す。読む前にsizeを確認する
  • varの付け忘れと参照型であることが二大バグ要因

配列を使いこなせると、複数シグナルの管理や独自の集計ロジックが書けます。次のステップとして、strategy()での本格的なバックテスト設計に進むと、ここで作った集計をそのまま検証に組み込めます。

なお、集計ロジックを複数並べて比べたくなると、無料プランの1チャートあたり2個というインジケーター上限がすぐに窮屈です。Essentialなら5個、Plusなら10個まで同時に表示でき、条件を変えた複数バージョンを並べて検証できます。

📊 TradingViewプラン比較

プラン 月額 主な特徴
Basic(無料) $0 1チャート・広告あり・インジケーター3つ
Essential $12.95〜 広告なし・インジケーター5つ・アラート20件
Plus ⭐人気 $24.95〜 インジケーター10個・アラート100件・同期チャート2つ
Premium $49.95〜 インジケーター25個・アラート無制限・同期チャート8つ
TradingViewを無料で始める

まずは無料プランで試して、必要なら有料プランへアップグレード

タイトルとURLをコピーしました