Az eddigi programok külön változókban tárolták az árakat, számlálókat és beállításokat. Ezzel sokféle indikátort és stratégiát el tudsz készíteni. Nagyobb programban azonban érdemes lehet egy egységként kezelni több összetartozó adatot: például egy ársáv két határát vagy egy jelzés árát és időpontját.

Erre szolgál a felhasználói típus, angolul user-defined type, röviden UDT. A metódus pedig egy adott típushoz tartozó művelet. Mindkettő a Pyne code része, de a könyv további példáihoz nem szükséges használnod őket. Ha először a stratégiákig szeretnél eljutni, továbbléphetsz a hibakereséshez, és később visszatérhetsz ide.

Összetartozó adatok egy típusban

Egy ársávot két szám ír le: az alsó és a felső határa. Saját típusban nevet adhatunk ennek az adatpárnak:

@udt
class PriceRange:
    bottom: float = 0.0
    top: float = 0.0

A class új típust definiál, a neve PriceRange. A bottom és a top a típus mezői. Mindkettő tört értéket is tárolhat, és az alapértékük nulla.

Az @udt jelölés teszi ezt Pyne felhasználói típussá. A udt nevet a pynecore.core.pine_udt modulból importáljuk. Az Edge-ben a típus definíciójába a mezőket írjuk; a hozzá tartozó metódusokat külön definiáljuk. Öröklődést ebben a nyelvi keretben nem használunk.

A típus és a belőle létrehozott objektum

A típus leírja, milyen adatokat fogunk együtt tárolni. Egy konkrét adatpárt a típusból létrehozott objektum, más néven példány tárol:

current_range = PriceRange()
current_range.bottom = low
current_range.top = high

A PriceRange() új példányt készít a mezők alapértékeivel. A következő két sor az alsó határát a mostani gyertya minimumára, a felsőt a maximumára állítja. A current_range.bottom és a current_range.top alakban olvashatod ki a mezőit. A pont itt egy mező elérését jelöli.

Ugyanabból a típusból több objektumot is létrehozhatsz. Például egy másik PriceRange más ársávot tárolhat; a mezőneveik azonosak, az értékeik egymástól függetlenek.

Metódus: művelet egy adott típuson

Az ársáv közepét a két határ átlagával kapjuk meg. Ezt a típushoz tartozó metódusként is megírhatjuk:

@method
def midpoint(self: PriceRange) -> float:
    return (self.bottom + self.top) / 2

Az @method metódusként jelöli meg a függvényt. Az első paraméter típusa mondja meg, milyen adaton dolgozik: itt egy PriceRange objektumon. A self annak az objektumnak a neve a metóduson belül, amelynek a közepét ki akarjuk számítani. Ez egy paraméternév; a típusát a PriceRange jelölés adja meg.

A definíció a class blokkon kívül, a fájl legkülső szintjén áll. A method nevet a pynecore.core.pine_method modulból importáljuk.

A hívás:

center = midpoint(current_range)

Az ismert metódust a nevével hívjuk, és első argumentumként átadjuk az objektumot, amelyen dolgozik. A self paraméter így a current_range objektumot kapja. A visszaadott szám a center változóba kerül.

Ezt a számítást egyszerű függvénnyel is megírhatnád. A metódus akkor segít a program szervezésében, ha több, egy adott típushoz tartozó műveleted van, és egyértelművé szeretnéd tenni, milyen adaton dolgoznak.

A teljes program

Hozd létre az udt_ranges.py fájlt a Scripts csoportban:

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

Futtasd a demóadaton. A kék vonal a gyertyák minimumát, a narancssárga a maximumát, a zöld a kettő közepét mutatja. A zöld pont minden gyertyán a másik két érték között van.

A PyneIDE-ben a PriceRange felhasználói típus és a midpoint metódus kódja látható; a grafikonon az alsó és felső árhatár, valamint a középár három vonala jelenik meg.
A PriceRange két mezője a gyertya határait tárolja, a midpoint metódus ezek közepét számítja ki.

A program minden gyertyán új PriceRange objektumot készít. Ebben a példában a mezők nem őrzik az előző gyertya értékeit; mindig a mostani gyertya két határát tárolják.

Értékadás és másolás

Egy objektumot egy másik változóhoz rendelni nem ugyanaz, mint lemásolni:

other_range = current_range
other_range.top = other_range.top + 1.0

A két változó ugyanarra az objektumra hivatkozik. A második sor ezért a current_range.top értékét is megváltoztatja.

Ha külön példányt szeretnél ugyanazokkal a mezőértékekkel, hozz létre egy új objektumot:

other_range = PriceRange()
other_range.bottom = current_range.bottom
other_range.top = current_range.top
other_range.top = other_range.top + 1.0

Ennél a két számból álló típusnál az új példány felső határának módosítása nem változtatja meg az eredetiét. A két számot külön objektumban tároljuk. Ha később egy mezőben másik objektumot vagy tömböt tárolsz, annak átadása egy új példánynak ugyanúgy hivatkozást ad át, és nem másolja le külön a tartalmát.

Objektum megőrzése a gyertyák között

Ha ugyanazt az objektumot szeretnéd tovább használni, a korábban tanult Persistent jelölést is alkalmazhatod. Ehhez importáld a Persistent nevet a pynecore modulból.

current_range: Persistent[PriceRange] = PriceRange()
current_range.bottom = low
current_range.top = high

Az objektum egyszer jön létre, a két mezőjét pedig minden gyertyán frissítjük. A main() elején lévő objektumlétrehozást és a két mező értékadását cserélheted erre a három sorra. A közép számítása és a rajzolás változatlan marad.

Ebben a példában ettől a grafikon nem változik: ugyanazokat a mezőértékeket adjuk meg. Más feladatban a mezőket csak egy eseménynél módosíthatod, így például egy korábban kijelölt ársávot is megőrizhetsz. A típus önmagában nem teszi perzisztenssé az objektumot.

Próbáld ki önállóan

Készíts width nevű metódust, amely visszaadja az ársáv szélességét. A midpoint mintájára írj egy self: PriceRange paramétert, és a két mezőből számold ki az eredményt.

A feladat megoldása
@method
def width(self: PriceRange) -> float:
    return self.top - self.bottom

A hívása a main() belsejében:

range_width = width(current_range)

Ha egy gyertyán a maximum 108, a minimum 101, akkor a szélesség 7. A metódus csak kiolvassa a mezőket; egyik határt sem módosítja.