ローソク足の値幅を求めるhigh - lowは、すでに学びました。何か所でも使うなら、自分の関数にできます。呼び出しから計算の目的が分かり、式を1か所にまとめられます。

関数の入力と結果

def price_range(top: float, bottom: float) -> float:
    return top - bottom

defは関数を定義します。名前はprice_rangeです。topとbottomという2つの入力を受け取ります。どちらも、小数も扱える数です。

-> floatは結果の型を示します。returnは結果を計算して返し、その関数呼び出しを終えます。

price_range(105, 100)は5を返します。関数自体は、それが価格なのかを知りませんし、ローソク足を選びもしません。呼び出す側が2つの値を渡します。

本体は4個のスペースで字下げします。この例の定義は、main()と同じくファイルの一番外側にあります。必要な2つの価格は、パラメーターで渡します。main()の中にも関数を作れます。後で試します。

名前のスコープ

topとbottomは、price_range内だけで使うパラメーターです。main()から直接参照はできません。そちらでは、返された結果を使います。

名前のスコープとは、コードのどこでその名前を使えるかという範囲です。別々の関数で同じパラメーター名を使っても、共通の1つの変数にはなりません。

ファイルの最外層で定義したprice_rangeから、main()のローカル変数を自動的に参照することはできません。ただし、ファイル冒頭でインポートしたcloseなどは使えます。参照できる範囲には、関数を定義する場所も関係します。

定義と呼び出しも別です。定義はルールを決め、呼び出しは具体的な値でそれを実行します。

プログラム全体

price_functions.pyを作ります。同じ関数で、ローソク足全体の値幅と、始値から終値への符号付きの変化を求めます。

"""
@pyne edge
"""
from pynecore.lib import close, color, high, low, open, plot, script


def price_range(top: float, bottom: float) -> float:
    return top - bottom


@script.indicator("Price Functions", overlay=False)
def main():
    full_range = price_range(high, low)
    body_change = price_range(close, open)
    plot(full_range, "Full range", color=color.blue)
    plot(body_change, "Body change", color=color.orange)

デモデータで実行します。高値は安値以上なので、全体の値幅は負になりません。始値と終値の差は負になる場合もあります。関数は渡された価格を変えず、2つの数の差を返すだけです。

price_functions.pyで関数を2回呼び、ローソク足全体の値幅と、実体の符号付きの変化を描画している。
同じ自作関数が、異なる入力から2つの結果を計算します。

この関数は短く、手計算でも確認できます。大きなプログラムでは、より複雑な部分処理にも名前を付けられますが、単純な足し算まで必ず関数にする必要はありません。

パラメーターの順序

price_range(high, low)は、highをtopに、lowをbottomに渡します。逆にすれば、引き算の符号も逆になります。

名前を指定して渡すこともできます。

full_range = price_range(top=high, bottom=low)

長くなりますが、複雑な呼び出しでは読みやすくなることがあります。名前は、定義したパラメーター名と完全に一致させます。

main()内の関数

内側に定義した関数は、それを囲む関数の変数も参照できます。複数の計算で同じ入力設定を使う場合などに便利です。

前のプログラムの2本の線を、移動平均で滑らかにします。長さを1つの入力で指定し、両方で使います。内側の関数には価格2つだけを渡し、長さはmain()のパラメーターから読みます。

price_functions.pyでは、外側のprice_rangeと指標のデコレーターを残してください。pynecore.libのインポートにinputとtaを加え、main()を次に置き換えます。

def main(
    length: int = input.int(5, title="Average length", minval=1, maxval=200),
):
    def average_difference(top: float, bottom: float) -> float:
        return ta.sma(price_range(top, bottom), length)

    average_range = average_difference(high, low)
    average_body_change = average_difference(close, open)
    plot(average_range, "Average range", color=color.blue)
    plot(average_body_change, "Average body change", color=color.orange)

lengthは、すでに学んだ整数の入力です。内側のaverage_differenceが受け取るのはtopとbottomの2つで、呼び出し時に値を渡します。lengthは周囲のmain()から読むので、呼び出しごとに渡す必要はありません。

内側の定義はmain()の一部なので、4個のスペースで始まります。その本体であるreturn行は8個です。average_differenceはmain()内で使える名前で、ファイルの最外層からは使えません。

price_rangeが価格差を求め、ta.smaがその移動平均を返します。青は全体の値幅の平均、オレンジは始値から終値への符号付きの変化の平均です。両方をすべての足で呼ぶため、どちらの平均も連続して計算します。

実行して歯車からInputsを開きます。Average lengthを5から20にし、Saveをクリックしてください。開いているチャートが自動で再計算され、両方が20本の平均に変わります。内側の関数の2つの呼び出しは変えずに済みました。長さを1にすれば、元のプログラムの線に戻ります。

こうした外側の変数への参照をクロージャー、英語ではclosureと呼びます。値を読めるからといって、内側で代入すれば自動的に外側の変数を書き換えられるわけではありません。この例ではlengthを読むだけです。

パラメーターで渡す形式なら、すべての入力が呼び出しから分かり、別の価格の組み合わせにも使いやすくなります。内側の関数は、複数の計算がmain()内の同じ変数を使う場合に便利です。

自作関数も状態を持てる

price_rangeは、受け取った値だけで計算します。足の間で保持する変数も履歴の参照もないので、2つの値を渡していつでも呼べます。

一方、average_differenceは状態を使っています。内部のta.smaが過去の値から計算するためです。自作関数の中で、直接PersistentやSeries変数を作ることもできます。

イベントを数える関数

陽線と陰線を別々に数えます。論理値を受け取り、今まで真が何回来たかを覚える関数を作ります。

ファイル冒頭でPersistentをインポートします。

from pynecore import Persistent

次の関数を、ファイルの一番外側、main()より前に書きます。

def count_events(event: bool) -> int:
    count: Persistent[int] = 0
    if event:
        count += 1
    return count

countの初期値は0です。eventが真なら1増やし、偽ならそのままにします。どちらでもreturnで累計を返します。呼び出しが終わっても、Persistentの値は次の呼び出しまで残ります。

overlay=Falseを残し、main()の定義と本体を次に置き換えます。

def main():
    bullish_count = count_events(close > open)
    bearish_count = count_events(close < open)
    plot(bullish_count, "Bullish count", color=color.green)
    plot(bearish_count, "Bearish count", color=color.red)

同じ関数を、コードの異なる2か所から呼んでいます。各呼び出し位置には別々のcountの状態があります。1つ目は陽線、2つ目は陰線を数えます。1つ目の呼び出しが、2つ目のカウンターを増やすことはありません。

例えば最初の4本が次の順序なら、結果はこうなります。

足始値と終値の関係bullish_countbearish_count
1本目終値が高い10
2本目終値が低い11
3本目同じ11
4本目終値が高い21

デモデータで実行します。緑の線は陽線で、赤は陰線でのみ1段上がります。新しい実行では、両方を0から数え直します。

両方の関数をすべての足で呼び、増加の条件は関数の中に置いています。状態を使う関数では、呼び出しの順序や頻度も動作の一部です。一部の足だけで呼ぶと、内部の履歴もその呼び出しのときだけ進みます。

共通のカウンターが1つだけ必要なら、1回呼んで結果を変数に入れ、複数の場所で使います。別の位置から呼べば、別のカウンターになります。

関数内に複数の文を書く

途中の計算が必要なら、returnより前に書けます。

def midpoint(top: float, bottom: float) -> float:
    total = top + bottom
    return total / 2

totalはローカル変数です。呼び出しが終わっても、main()の変数にはなりません。関数から返すのは計算結果だけです。

自分で試す

midpoint関数を加え、ローソク足の中間値を描く重ね表示の指標を作ってください。高値と安値を、topとbottomで受け取ります。

解答

上のmidpointをmain()より前に書きます。指標のデコレーターはoverlay=Trueにし、本体を次にします。

center = midpoint(high, low)
plot(center, "Midpoint", color=color.blue, linewidth=2)

前の2本の計算と描画は、この版から削除できます。使わなくなったインポートも除いてください。

呼び出しを説明する

price_range(close, open)で、どの値がどのパラメーターに入り、returnで何が起こり、main()のどこで結果を受け取るか説明してください。長い関数呼び出しでも、この3つの手順を追えます。