Znasz już obliczenie zakresu świecy: high - low. Jeśli potrzebujesz go w wielu obliczeniach, możesz wydzielić funkcję. Nazwa wywołania może wyjaśniać cel, a wzór utrzymujesz w jednym miejscu.

Wejścia i wynik funkcji

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

def definiuje funkcję price_range. Przyjmuje dwa wejścia, top i bottom, oba liczbowe i dopuszczające ułamki.

-> float określa typ wyniku. return oblicza i zwraca wynik, kończąc dane wywołanie.

price_range(105, 100) daje 5. Funkcja nie wie, czy liczby są cenami, i nie wybiera świecy. Wartości podaje wywołujący.

Ciało funkcji ma cztery spacje wcięcia. Definicja stoi obok main() na zewnętrznym poziomie pliku, więc potrzebne ceny przekazujemy parametrami. Funkcję można też zdefiniować wewnątrz main(); spróbujemy poniżej.

Zasięg nazwy

top i bottom są parametrami dostępnymi wyłącznie w price_range. main() nie może odwołać się do nich bezpośrednio; używa zwróconego wyniku.

Zasięg nazwy określa część kodu, w której jest dostępna. Dwie funkcje mogą używać identycznych nazw parametrów bez tworzenia jednej wspólnej zmiennej.

price_range zdefiniowane na poziomie pliku nie widzi automatycznie lokalnych zmiennych main(). Może jednak korzystać z importów na początku pliku, na przykład close. Widoczność zależy więc też od miejsca definicji.

Definicja i wywołanie to nie to samo. Definicja opisuje regułę, a wywołanie wykonuje ją z konkretnymi wartościami.

Pełny program

Utwórz price_functions.py. Ta sama funkcja obliczy pełny zakres świecy i zmianę od otwarcia do zamknięcia ze znakiem:

"""
@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)

Uruchom na demonstracji. Pełny zakres nie jest ujemny, bo maksimum jest co najmniej równe minimum. Zmiana otwarcia do zamknięcia może być ujemna. Funkcja nie modyfikuje cen; zwraca ich różnicę.

Dwa wywołania w price_functions.py rysują pełny zakres i zmianę korpusu ze znakiem.
Ta sama własna funkcja oblicza dwa wyniki z różnych wejść.

Funkcja w przykładzie jest krótka i łatwa do ręcznego sprawdzenia. W większym programie możesz nazwać bardziej złożone podzadania, ale nie musisz wydzielać pojedynczego dodawania.

Kolejność parametrów

price_range(high, low) przekazuje high jako top, a low jako bottom. Zamiana odwraca znak odejmowania.

Możesz też podać nazwę parametru:

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

To dłuższy zapis, ale bywa czytelniejszy przy złożonych wywołaniach. Nazwa musi dokładnie odpowiadać parametrowi definicji.

Funkcja wewnątrz main()

Funkcja zdefiniowana wewnątrz innej ma dostęp także do jej zmiennych. Przydaje się to, gdy kilka obliczeń korzysta z tego samego ustawienia wejściowego.

Wygładźmy obie linie średnią kroczącą. Jej długość określimy jednym wejściem używanym w obu obliczeniach. Do funkcji wewnętrznej przekażemy tylko ceny, a długość odczyta z parametrów main().

W price_functions.py zachowaj zewnętrzne price_range i dekorator. Dodaj input i ta do importu z pynecore.lib, a definicję main() zastąp:

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 to poznane wejście całkowitoliczbowe. Wewnętrzne average_difference dostaje top i bottom od wywołania, a length odczytuje z otaczającego main(). Nie trzeba go ponownie przekazywać.

Definicja wewnętrzna ma cztery spacje, bo należy do main(). Własne ciało, wiersz return, ma osiem. average_difference jest dostępne wewnątrz main(), nie na zewnętrznym poziomie pliku.

price_range liczy różnicę, ta.sma zwraca jej średnią. Niebieska linia jest średnią pełnego zakresu, pomarańczowa średnią zmiany korpusu ze znakiem. Oba wywołania działają na każdej świecy, więc średnie liczą się nieprzerwanie.

Uruchom wersję, otwórz Inputs kołem zębatym, zmień Average length z 5 na 20 i kliknij Save. Otwarty wykres automatycznie przeliczy obie linie na dwudziestoświecowe. Nie trzeba było zmieniać wywołań. Ustawiając długość 1, odzyskasz linie pierwotnego programu.

Ten dostęp do zewnętrznych zmiennych nazywa się domknięciem, po angielsku closure. Możliwość odczytu nie oznacza, że przypisanie wewnątrz automatycznie nadpisze zmienną zewnętrzną. Tutaj tylko odczytujemy length.

W wersji parametrycznej wszystkie przekazane wartości widać w wywołaniu i łatwo użyć innej pary cen. Wersja wewnętrzna bywa wygodna, gdy kilka obliczeń korzysta z tych samych zmiennych main().

Własna funkcja też może mieć stan

price_range oblicza tylko z otrzymanych wartości. Nie ma zachowywanej zmiennej ani historii, więc można ją dowolnie wywoływać z dwiema wartościami.

Powyższe average_difference już ma stan, bo jego ta.sma liczy z przeszłości. Własna funkcja może też bezpośrednio deklarować Persistent lub Series.

Funkcja zliczająca zdarzenia

Policzmy osobno świece wzrostowe i spadkowe. Funkcja otrzyma wartość logiczną i zapamięta, ile razy dotąd dostała prawdę.

Na początku pliku importuj Persistent:

from pynecore import Persistent

Przed main(), na poziomie pliku, dodaj:

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

count zaczyna od zera. Prawdziwe event zwiększa go o jeden, fałszywe zachowuje wartość. return zawsze zwraca dotychczasową sumę. Po zakończeniu wywołania Persistent zachowuje wynik na następny raz.

Zostaw overlay=False. Zastąp definicję i ciało 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)

Wywołujemy tę samą funkcję z dwóch miejsc kodu. Każde ma osobny stan count: pierwsze liczy wzrosty, drugie spadki. Pierwsze nie zwiększa drugiego licznika.

Jeśli pierwsze cztery świece wystąpią w podanej kolejności, otrzymasz:

ŚwiecaRelacja otwarcia i zamknięciabullish_countbearish_count
PierwszaZamknięcie wyżej10
DrugaZamknięcie niżej11
TrzeciaCeny równe11
CzwartaZamknięcie wyżej21

Uruchom na demonstracji. Zielona linia zwiększa się tylko przy wzrostowej, czerwona przy spadkowej świecy. Nowy przebieg buduje oba liczniki od zera.

Oba wywołania działają na każdej świecy; warunek zwiększenia jest wewnątrz. Dla funkcji stanowych kolejność i częstość wywołań należą do zachowania. Wywoływana tylko na części świec historia przesuwa się wyłącznie przy tych wywołaniach.

Jeśli potrzebujesz jednego wspólnego licznika, wywołaj funkcję raz i używaj zmiennej wynikowej w wielu miejscach. Drugie miejsce wywołania oznacza nowy osobny licznik.

Kilka instrukcji w funkcji

Wynik pośredni możesz obliczyć przed return:

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

total jest lokalne i nie staje się zmienną main() po zakończeniu wywołania. Z funkcji przekazaliśmy tylko wynik.

Spróbuj samodzielnie

Dodaj funkcję midpoint, potem utwórz wskaźnik overlay rysujący środek świec. Parametry top i bottom mają otrzymywać maksimum i minimum.

Rozwiązanie zadania

Wpisz powyższe midpoint przed main(). W dekoratorze ustaw overlay=True, a w ciele:

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

Możesz usunąć obliczenia i rysowanie wcześniejszych linii z tej wersji. Usuń też zbędne importy.

Wyjaśnij wywołanie

Dla price_range(close, open) opowiedz, która wartość trafia do którego parametru, co robi return i gdzie main() otrzymuje wynik. Te trzy kroki można śledzić także przy dłuższych wywołaniach.