発展:ユーザー定義型とメソッド
関連するデータを独自の型にまとめ、その型を操作する処理を作ります。
これまでは、価格、カウンター、設定を別々の変数に入れていました。それでも多くの指標やストラテジーを作れます。ただ、大きなプログラムでは、関連するデータをまとめたいことがあります。例えば価格帯の上下限や、シグナルの価格と時刻です。
そのために使うのがユーザー定義型(user-defined type、略してUDT)です。メソッドは、ある型に対応する操作です。どちらもPyne codeの一部ですが、この本のその先の例題に必須ではありません。先にストラテジーまで進みたければ、デバッグへ進み、後で戻ってもかまいません。
関連するデータを型にまとめる
価格帯は、下限と上限の2つの数で表せます。独自の型で、この組に名前を付けます。
@udt
class PriceRange:
bottom: float = 0.0
top: float = 0.0classが新しい型を定義し、名前はPriceRangeです。bottomとtopはフィールドです。どちらも小数を保存でき、既定値は0です。
@udtによって、Pyneのユーザー定義型になります。udtはpynecore.core.pine_udtから読み込みます。Edgeでは型の定義内にフィールドを書き、メソッドは別に定義します。この範囲の言語機能では、継承は使いません。
型と、型から作るオブジェクト
型は、一緒に保存するデータの構成を定めます。具体的な値を入れるのは、その型から作ったオブジェクト、またはインスタンスです。
current_range = PriceRange()
current_range.bottom = low
current_range.top = highPriceRange()が、フィールドに既定値を持つ新しいインスタンスを作ります。次の2行で、下限を現在の安値、上限を高値にします。フィールドはcurrent_range.bottomやcurrent_range.topで読み取ります。ピリオドは、フィールドへのアクセスを表します。
同じ型から複数のオブジェクトを作れます。別のPriceRangeが異なる価格帯を保持していてもかまいません。フィールド名は同じでも、値は独立しています。
メソッド:特定の型に対する操作
価格帯の中間値は、上下限の平均です。型に対応するメソッドとして書けます。
@method
def midpoint(self: PriceRange) -> float:
return (self.bottom + self.top) / 2@methodは、関数をメソッドとして指定します。最初のパラメーターの型が、対象データを示します。ここではPriceRangeです。selfは、中間値を求める対象オブジェクトを、メソッド内部で呼ぶ名前です。これはパラメーター名であり、型はPriceRangeで指定しています。
この定義はclassの外、ファイルの最外層に書きます。methodはpynecore.core.pine_methodからインポートします。
呼び出しは次の形です。
center = midpoint(current_range)既知のメソッドを名前で呼び、対象のオブジェクトを最初の引数に渡します。selfはcurrent_rangeを受け取り、返された数値をcenterに代入します。
普通の関数でも同じ計算を書けます。特定の型に関連する操作が複数あり、どのデータを扱うか明確にしたいときに、メソッドが整理に役立ちます。
プログラム全体
Scriptsにudt_ranges.pyを作ります。
"""
@pyne edge
"""
from pynecore.core.pine_method import method
from pynecore.core.pine_udt import udt
from pynecore.lib import color, high, low, plot, script
@udt
class PriceRange:
bottom: float = 0.0
top: float = 0.0
@method
def midpoint(self: PriceRange) -> float:
return (self.bottom + self.top) / 2
@script.indicator("Price Range Objects", overlay=True)
def main():
current_range = PriceRange()
current_range.bottom = low
current_range.top = high
center = midpoint(current_range)
plot(current_range.bottom, "Lower", color=color.blue)
plot(current_range.top, "Upper", color=color.orange)
plot(center, "Midpoint", color=color.green, linewidth=2)デモデータで実行してください。青が安値、オレンジが高値、緑が中間値です。各ローソク足で、緑の点は他の2つの間にあります。

各ローソク足で新しいPriceRangeを作っています。この例のフィールドは前の値を残さず、現在の上下限を保存します。
代入とコピー
別の変数にオブジェクトを代入しても、コピーにはなりません。
other_range = current_range
other_range.top = other_range.top + 1.0両方の変数は、同じオブジェクトを参照します。そのため2行目で、current_range.topも変わります。
同じフィールド値を持つ別のインスタンスが欲しければ、新しく作ります。
other_range = PriceRange()
other_range.bottom = current_range.bottom
other_range.top = current_range.top
other_range.top = other_range.top + 1.0数値2つからなるこの型では、新しいインスタンスの上限を変えても、元の上限は変わりません。別々のオブジェクトに数値を保存しているためです。後でフィールドに別のオブジェクトや配列を入れた場合、それを新しいインスタンスに渡しても参照が渡されるだけで、中身が別にコピーされるわけではありません。
ローソク足の間でオブジェクトを保持する
同じオブジェクトを使い続けたければ、Persistentも使えます。pynecoreからPersistentをインポートしてください。
current_range: Persistent[PriceRange] = PriceRange()
current_range.bottom = low
current_range.top = highオブジェクトは一度だけ作り、フィールドは各ローソク足で更新します。main()冒頭の作成と2つの代入を、この3行に置き換えられます。中間値の計算と描画はそのままです。
この例では同じフィールド値を入れるので、チャートは変わりません。別の用途では、イベント時だけフィールドを変えることで、以前に決めた価格帯などを保持できます。型を定義しただけで、オブジェクトが永続化されるわけではありません。
自分で試す
価格帯の幅を返すwidthメソッドを作ってください。midpointと同様にself: PriceRangeを受け取り、フィールド2つから計算します。
解答
@method
def width(self: PriceRange) -> float:
return self.top - self.bottommain()内の呼び出しです。
range_width = width(current_range)高値が108、安値が101なら、幅は7です。メソッドはフィールドを読むだけで、上下限を変えません。