Dla zaawansowanych: własne typy i metody
Połączysz powiązane dane we własnym typie i napiszesz operację na nim.
Dotąd ceny, liczniki i ustawienia były osobnymi zmiennymi. To wystarcza do wielu wskaźników i strategii. W większym programie warto jednak czasem potraktować powiązane dane jako całość: na przykład granice pasma albo cenę i czas sygnału.
Służy do tego typ użytkownika, po angielsku user-defined type, czyli UDT. Metoda jest operacją związaną z konkretnym typem. Oba mechanizmy należą do Pyne code, ale nie są potrzebne w dalszych przykładach. Jeśli chcesz najpierw dojść do strategii, przejdź do debugowania i wróć później.
Powiązane dane w jednym typie
Zakres cen opisują dwie liczby: dolna i górna granica. Własny typ może nazwać tę parę:
@udt
class PriceRange:
bottom: float = 0.0
top: float = 0.0class definiuje typ PriceRange. bottom i top to jego pola. Oba dopuszczają ułamki i domyślnie mają zero.
@udt oznacza typ użytkownika Pyne. udt importujemy z pynecore.core.pine_udt. W Edge wewnątrz typu definiujemy pola, a metody osobno. W tym zakresie języka nie korzystamy z dziedziczenia.
Typ i utworzony z niego obiekt
Typ opisuje wspólnie przechowywane dane. Konkretną parę trzyma utworzony z niego obiekt, nazywany też instancją:
current_range = PriceRange()
current_range.bottom = low
current_range.top = highPriceRange() tworzy nową instancję z domyślnymi polami. Kolejne wiersze ustawiają dolną granicę na bieżące minimum, górną na maksimum. Pola odczytujesz jako current_range.bottom i current_range.top. Kropka oznacza dostęp do pola.
Z typu możesz utworzyć wiele obiektów. Inne PriceRange może przechowywać inny zakres; nazwy pól są te same, wartości niezależne.
Metoda: operacja na danym typie
Środek zakresu to średnia granic. Można go zapisać jako metodę typu:
@method
def midpoint(self: PriceRange) -> float:
return (self.bottom + self.top) / 2@method oznacza funkcję jako metodę. Typ pierwszego parametru określa dane, na których pracuje: tutaj obiekt PriceRange. self jest nazwą tego obiektu wewnątrz metody. To nazwa parametru, a jego typ podaje PriceRange.
Definicja stoi poza blokiem class, na poziomie pliku. method importujemy z pynecore.core.pine_method.
Wywołanie:
center = midpoint(current_range)Znaną metodę wywołujemy po nazwie, przekazując obiekt jako pierwszy argument. self otrzymuje więc current_range, a zwrócona liczba trafia do center.
To samo można napisać zwykłą funkcją. Metoda pomaga organizować kod, gdy masz wiele operacji na konkretnym typie i chcesz jasno wskazać dane, których dotyczą.
Pełny program
Utwórz udt_ranges.py w Scripts:
"""
@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)Uruchom na demonstracji. Niebieski pokazuje minima, pomarańczowy maksima, zielony środek. Zielony punkt zawsze leży między pozostałymi wartościami.

Na każdej świecy powstaje nowe PriceRange. Pola nie zachowują poprzednich cen, tylko granice obecnej świecy.
Przypisanie i kopiowanie
Przypisanie obiektu do drugiej zmiennej nie jest kopią:
other_range = current_range
other_range.top = other_range.top + 1.0Obie zmienne wskazują ten sam obiekt, więc drugi wiersz zmienia też current_range.top.
Osobną instancję z takimi samymi polami utworzysz tak:
other_range = PriceRange()
other_range.bottom = current_range.bottom
other_range.top = current_range.top
other_range.top = other_range.top + 1.0Dla typu z dwoma liczbami zmiana górnej granicy nowej instancji nie zmienia oryginału: liczby są w osobnych obiektach. Jeśli później pole zawiera inny obiekt lub tablicę, przypisanie go nowej instancji też przekazuje referencję, nie kopiuje zawartości.
Zachowanie obiektu między świecami
Możesz użyć poznanego Persistent, aby dalej wykorzystywać ten sam obiekt. Importuj Persistent z pynecore.
current_range: Persistent[PriceRange] = PriceRange()
current_range.bottom = low
current_range.top = highObiekt powstaje raz, a pola aktualizujemy co świecę. Te trzy wiersze mogą zastąpić tworzenie obiektu i przypisania na początku main(). Obliczenie środka i rysowanie pozostają.
Wykres tu się nie zmienia, bo wpisujemy te same pola. W innym zadaniu możesz zmieniać je tylko po zdarzeniu i zachowywać wcześniej wybrany zakres. Sam typ nie nadaje obiektowi trwałości.
Spróbuj samodzielnie
Napisz metodę width, która zwraca szerokość zakresu. Jak w midpoint, użyj self: PriceRange i policz wynik z pól.
Rozwiązanie zadania
@method
def width(self: PriceRange) -> float:
return self.top - self.bottomWywołanie w main():
range_width = width(current_range)Przy maksimum 108 i minimum 101 szerokość wynosi 7. Metoda tylko czyta pola, nie zmienia granic.