Pine Scriptのコードを読んでいると、`type`で定義した型に対して、
`.`(ドット)で関数を呼んでいる書き方を見かけることがあります。
「MQLの構造体(struct)と同じようなものだろう」と、思ったことはありませんか。
実は、構造体にはできないことが、Pine Scriptのtypeにはできます。
既刊struct-mql4-mql5.mdと、
mql5-pointer-object-cobject.mdでは、
MQLの構造体とオブジェクトポインタを解説しました。
この記事では、Pine Scriptのtype(ユーザー定義型)とmethodを、
この2つの既刊記事と対比しながら解説します。
・typeキーワードでのユーザー定義型(UDT)の宣言方法
・methodキーワードでの関数の紐付け方法
・MQLの構造体(データのみ)との違い
・Pine Scriptのオブジェクトが常に参照型として振る舞う理由
type・methodの基本とMQLの対応
まず、それぞれの基本的な書式を確認します。
Pine Scriptのtype・methodの書式
type StreakCounter
int count = 0
int direction = 0
method update(StreakCounter s, float diff) =>
newDirection = diff > 0 ? 1 : diff < 0 ? -1 : 0
s
typeでフィールドを持つ型を定義し、methodでその型専用の関数を定義します。
MQLではstruct(構造体)と関数を別々に書く
struct StreakCounter
{
int count;
int direction;
};
void UpdateStreak(StreakCounter &s, double diff)
{
int newDirection = diff > 0 ? 1 : (diff < 0 ? -1 : 0);
}
既刊struct-mql4-mql5.mdのとおり、
MQLの構造体はデータの集まりでしかなく、
更新用の関数(UpdateStreak)は構造体の外に、
別の独立した関数として書く必要があります。
最大の違い: データだけの構造体 vs 関数まで持てる型
ここが、MQLユーザーが最初に戸惑うポイントです。
Pine Scriptはtypeとmethodがセットで完結する
・methodはそのtypeの第1パラメータに紐付く専用関数
・呼び出しはstreak.update(diff)のようにドット記法で書ける
・MQL5のクラス(CTrade等のメンバー関数)に近い感覚
MQLで同じことをするにはクラス(class)が必要
MQLで「データ+専用関数」をひとまとめにするには、structではなくclassを使い、
メンバー関数として定義する必要があります。
既刊ctrade-mql5.mdで解説したCTradeクラスも、
この仕組みで発注用の関数をまとめています。
structとclassは見た目が近くても、
MQLでは明確に別の仕組みとして教える必要がありました。
もう一つの違い: 参照渡しを&で書かなくていい理由
ここが、この記事で最も伝えたいポイントです。
MQLの構造体は&を付けないと更新が反映されない
・既刊pass-by-reference-value-mql4-mql5.mdのとおり
・構造体を関数に渡すと既定では値のコピーが渡る
・関数内で書き換えても、呼び出し元の構造体は変わらない
・&を付けて参照渡しにしないと更新が反映されない
Pine Scriptのtypeインスタンスは常に参照型
Pine Scriptのtypeで作ったオブジェクトは、
既刊mql5-pointer-object-cobject.mdの、
オブジェクトポインタと同じように、
常に「参照」として扱われます。
methodに渡したオブジェクトのフィールドを書き換えると、
呼び出し元の変数にもそのまま反映されるため、
MQLのように&を書く必要がありません。
ただし、フィールドへの直接書き込み(obj.field := 値)は、
method内のチェック処理を経由しない点に注意が必要です。
varを付けないと毎バー作り直される
既刊var/varipの記事と同じ注意点が、typeにも当てはまります。
var無しだと毎バー新しいインスタンスになる
既刊pinescript-var-varip-mql-comparison.mdで、
解説したとおり、
var宣言のない変数はバーごとに再評価されます。
StreakCounter.new()も例外ではなく、varを付けずに毎回呼ぶと、
count・directionの蓄積が毎バーリセットされてしまいます。
MQLの構造体はグローバル変数と同じように値を保持する
既刊struct-mql4-mql5.mdのとおり、
MQLの構造体はOnInit()で宣言すれば、
EA・インジケーターが動いている間、値を保持し続けます。
Pine Scriptのような「再生成」の心配はありません。
実際に動くサンプルコード: 連続本数を数えるStreakCounter
直近の値動きが何本連続で同じ方向かを数える設計です。
//@version=6
indicator("StreakCounter Sample", overlay = false)
type StreakCounter
int count = 0
int direction = 0
method update(StreakCounter s, float diff) =>
newDirection = diff > 0 ? 1 : diff < 0 ? -1 : 0
if newDirection == s.direction and newDirection != 0
s.count := s.count + 1
else
s.count := 1
s.direction := newDirection
s
var StreakCounter streak = StreakCounter.new()
streak.update(close - close[1])
plot(streak.count, title = "連続本数", color = color.blue)
var宣言したstreakを、methodの中でフィールドごと書き換えています。
呼び出し元の変数を直接書き換えなくても、参照型であるおかげで変化がそのまま反映されます。
なお、終値が前バーと全く同じ(diffが0)ときは、
newDirectionが0になり、countは1にリセットされる仕様です。
「方向なし」を連続とは数えない設計だと理解しておいてください。
TradingViewでの貼り付け・実行手順
実際にTradingViewで動かすまでの手順です。
- TradingViewのチャート画面下部にある「Pineエディタ」タブを開く
- 新規スクリプトを作成し、上のコードを貼り付ける
- 「保存」してから「チャートに追加」をクリックする
- 別ウィンドウに連続本数(count)のラインが表示されることを確認する
- var StreakCounterのvarを外して再度試し、ラインが1以上に増えなくなることも確認する
よくあるエラーと注意点
実装でつまずきやすいポイントをまとめます。
method内でobj.field=値と書いてエラーになる
・フィールドの再代入は=ではなく:=を使う
・=はtype宣言時のデフォルト値指定にのみ使う
・varと同じ「宣言は=、再代入は:=」というPine Script共通のルール
MQLのclassとstructを混同する
既刊struct-mql4-mql5.mdのstructは、
単純な型のみを持つ場合に「単純構造体」として相互コピーが可能ですが、
classはメンバー関数・継承・ポインタが絡むため、
同じ感覚でコピーできない場面が増えます。
よくある質問
type・methodについて、よく疑問に挙がる点に回答します。
・バーをまたいで保持したいオブジェクトには必ずvarを付ける
・フィールドの再代入は:=
・継続利用するオブジェクトはmethod経由で更新する
1つのtypeに複数のmethodを定義できますか?
できます。
update用・リセット用など、
目的ごとに複数のmethodを同じtypeに対して定義できます。
MQL5のクラスのように継承はできますか?
Pine Scriptのtypeには、MQL5のクラスのような継承の仕組みはありません。
既刊mql5-pointer-object-cobject.mdの、
CObject継承のような構造を、
そのまま移植することはできない点に注意が必要です。
配列の中にtypeのインスタンスを入れられますか?
入れられます。
既刊の配列関数の記事、
pinescript-array-functions-mql-comparison.md、
のarray関数と組み合わせ、
StreakCounterを複数まとめて配列で管理することもできます。
【Pine Script入門】type(ユーザー定義型)とmethodをMQLユーザー向けに解説してみた(構造体・クラスとの比較):まとめ
Pine Scriptのtypeは、MQLの構造体のようにフィールドを持てるだけでなく、
methodで専用の関数まで紐付けられる点が大きな違いです。
さらに、typeのインスタンスは常に参照型として振る舞うため、
MQLのように&を付けて参照渡しを明示する必要がありません。
ただし、var宣言を忘れると毎バー作り直されてしまう点は、
既刊var/varipの記事と同じ注意が必要です。
コメント