Egy indikátort könnyebb továbbgondolni, ha érted, hogyan készül az értéke. Mi kerül be a számításba? Mit jegyez meg a következő gyertyára? Mi változik, ha más átlagot vagy más árforrást használsz benne? A forráskód ezekre a kérdésekre is választ ad.

A PyneCore ta könyvtára Pyne code-ban íródott. A benne talált Persistent változók, függvényhívások és feltételek a könyvben tanult működést használják. A teljes forrás elérhető, ezért egy kész indikátort is ugyanúgy végigkövethetsz, mint a saját programodat.

Ez haladó fejezet: a forrás olvasását és a kísérletezés módját tanítja. Nem kell egyszerre az egész könyvtárat megértened. Kezdd egy rövid függvénnyel, majd kövesd tovább azokat a hívásokat, amelyekre épül.

Nyisd meg a tényleges implementációt

  1. Nyisd meg a korábban elkészített moving_averages.py fájlt a PyneIDE-ben.
  2. A ta.ema(close, 10) sorban macOS-en a Command, Windows vagy Linux alatt a Ctrl billentyűt lenyomva kattints az ema nevére. A jobb gombos menü Go to Definition művelete is használható.
  3. A szerkesztő megnyitja a telepített PyneCore ta.py fájlját az ema definíciójánál.
  4. Olvasd el a függvény leírását, majd a törzsét. Ha egy másik függvényt hív, annak definíciójához is továbbléphetsz.
A PyneIDE szerkesztőjében a ta.py könyvtári fájl ema definíciója és a kezdőértékről szóló leírása látható.
A definícióhoz ugrás a telepített ta.py fájl EMA-függvényét nyitja meg. A saját program külön fülön marad.

A paraméterek megtekintését és a definícióhoz ugrást a Segítség kódírás közben fejezetben már kipróbáltad. Ugyanez a művelet az RSI, a MACD és a könyvtár többi függvényénél is elvezet a forráshoz. A teljes ta referencia a függvények keresésében és paramétereik értelmezésében segít.

SMA: a képlet mögötti összegzés

A ta.sma számításának két sora:

length = int(length)
return lib_math.sum(source, length) / length

Az átlag az ablak összege osztva az elemszámmal. A hossz egész számmá alakítása után az összegzés a lib_math.sum feladata. Az állapotot itt nem az sma törzsében találod: a hívott összegző számítás vezeti. Ha tovább követed a hívást, a math.py modulon keresztül az _math_stateful.py megvalósításához jutsz.

Ez fontos olvasási szokás: attól, hogy egy függvény rövid, még lehet állapotos. A benne hívott függvények működése is része a számításnak. A kézzel megírt mozgóátlag ugyanennek az ablaknak az értékeit adta össze saját ciklussal.

EMA és RMA: a megőrzött átlag frissítése

Az EMA bemelegedés után ezt a lépést végzi:

last_val = last_val + alpha * (source - last_val)
return last_val

A last_val az előző átlag, a source a mostani érték. A kettő különbségének egy részével módosítjuk a régi átlagot. Az EMA-ban az alpha értéke 2 / (length + 1). Tízes hossznál tehát az új ár és a régi átlag különbségének körülbelül 18,18 százalékát adjuk hozzá a régi átlaghoz.

A forrásban mind az alpha, mind a last_val Persistent változó. A last_val kezdetben hiányzik, ezért a függvény először az azonos hosszúságú SMA-val próbálja beállítani:

if not (last_val == last_val):
    last_val = sma(source, length)
    return last_val

Amíg az SMA-hoz sincs elegendő adat, az eredmény hiányzik. A kezdeti átlag elkészülése után már az előző értéket frissítjük. A not (last_val == last_val) hiányvizsgálat: a hiányzó érték nem egyenlő önmagával. A saját példáidban ugyanerre a célra a na(value) hívást használtad.

Az RMA, vagyis Wilder simítása hasonlóan indul, de másképp frissül:

last_val = (last_val * (length - 1) + source) / length
return last_val

Tízes hossznál az új érték súlya egytized. Az EMA-nál ugyanezzel a hosszal nagyobb az új érték súlya. Ezért a két simítás eltérően reagál ugyanarra az ársorra.

A teljes forrásban a hiányzó bemenet kezelését és az egyes hosszhoz tartozó rövidítést is megtalálod. Két azonos képletet használó saját változat eredménye is eltérhet, ha más kezdőértékkel indul, másképp kezeli a hiányt, vagy nem ugyanazokon a gyertyákon fut le. Számít a lebegőpontos műveletek sorrendje is; pontos összehasonlításhoz azt is tartsd meg.

RSI: két külön simított változás

Az RSI a forrás növekedését és csökkenését külön kezeli. A PyneCore-ban ezek a fő számítási sorok:

rma_u = rma(builtins.max(source - prev_src, 0.0), length)
rma_d = rma(builtins.max(prev_src - source, 0.0), length)
prev_src = source

A prev_src az előző érvényes forrásérték, amelyet Persistent változó őriz. Ha az ár 100-ról 103-ra nő, a felfelé változás 3, a lefelé változás 0. Ha 103-ról 101-re csökken, ezek rendre 0 és 2. Az RMA a két sorozatot külön simítja; a két hívási helyhez külön állapot tartozik.

A prev_src frissítése a két különbség kiszámítása után történik. Ez ugyanaz a sorrend, amelyet a perzisztens előző érték szorgalmi feladatában is használtál.

A két simított változás arányából készül az RSI:

if rma_d <= 1e-10:
    return 100.0
if rma_u <= 1e-10:
    return 0.0

return 100 - 100 / (1 + rma_u / rma_d)

A lefelé mozgás hiánya 100-as, a felfelé mozgás hiánya 0-s értéket ad. A forrás a nagyon kicsi értékeket is kezeli, és először a lefelé oldalt vizsgálja: ha mindkét oldal a határ alatt van, az eredmény 100. Ezek a részletek egy puszta képletből nem derülnének ki.

Az első érvényes árból még nincs változás. Az RSI ezért előbb eltárolja azt, és csak a következő értékkel tudja elindítani a két simítást. Az indikátor bemelegedését a változások száma határozza meg. Az RSI nem valószínűséget mutat: egy 70-es érték nem jelenti az áremelkedés 70 százalékos esélyét.

MACD: mozgóátlagokból új indikátor

A MACD két EMA különbségét számítja, majd ennek is készít egy EMA-t. A fő számítási lépések, a hiányvizsgálatok nélkül:

fast = ema(source, fastlen)
slow = ema(source, slowlen)
macd_val = fast - slow
signal = ema(macd_val, siglen)

A három visszaadott érték:

return macd_val, signal, macd_val - signal

A MACD-vonal a gyors és lassú átlag különbsége. A jelzővonal ennek simított változata. A hisztogram a két vonal különbsége, ezért nulla, amikor a két vonal azonos értékű.

Egy hívás három eredményt ad, amelyeket három változóval vehetsz át:

macd_line, signal_line, histogram = ta.macd(close, 12, 26, 9)

A sorrend számít: MACD-vonal, jelzővonal, hisztogram. Amíg a két árátlag nem készült el, a különbség is hiányzik. Utána a jelzővonalnak még saját bemelegedése van. Egy összetett indikátor kezdeti hiányai így a belső számításokból is következnek.

ATR: az árugrás is a tartomány része

Az ATR megvalósítása rövid:

return rma(tr(True), length)

Az RMA-t már ismered; itt a True Range, magyarul valódi tartomány értékét simítja. Ez a következő három távolság maximuma:

  • a mostani maximum és minimum különbsége;
  • a mostani maximum távolsága az előző gyertya záróárától;
  • a mostani minimum távolsága az előző gyertya záróárától.

Ha az előző záróár 100, a mostani minimum 108, a maximum 110, akkor a gyertya saját tartománya csak 2. A valódi tartomány viszont max(2, 10, 8), vagyis 10: az előző zárástól bekövetkezett árugrást is figyelembe veszi.

A tr(True) első gyertyán, hiányzó előző záróár mellett a high - low értéket használja. A forrásban az előző záróárat egy futtató által biztosított belső adatból olvassa ki. Az ATR a mozgás nagyságát, az ár egységében mutatja; az irányát nem adja meg.

Bollinger-szalag: átlag és szórás

A ta.bb középvonala SMA, a két határ pedig ettől a szórás megadott többszörösével tér el. A forrás előbb kiszámítja a szórást és az átlagot:

std_dev = stdev(series, length)
middle = sma(series, length)

Az érvényes középértékhez tartozó visszatérés:

std_dev *= mult
return middle, middle + std_dev, middle - std_dev

A szórás azt írja le, mennyire szóródnak az ablak értékei az átlag körül. Ha a közép 100, a szórás 3, a szorzó 2, akkor a felső határ 106, az alsó 94. A hívás itt is három értéket ad vissza, de más sorrendben, mint a MACD: közép, felső határ, alsó határ.

Saját változat: először reprodukáld az eredetit

Hozd létre a bands_workshop.py fájlt. A saját függvényünk a könyvtár egyszerűbb részeiből állítja össze a szalagot. Mellette a kész ta.bb felső határát is kiszámítjuk, hogy legyen összehasonlítási alap.

"""
@pyne edge
"""
from pynecore.lib import close, color, input, plot, script, ta


def custom_bands(source: float, length: int, multiplier: float) -> tuple[float, float, float]:
    middle = ta.sma(source, length)
    deviation = ta.stdev(source, length)
    upper = middle + multiplier * deviation
    lower = middle - multiplier * deviation
    return middle, upper, lower


@script.indicator("Bands Workshop", overlay=True)
def main(
    length: int = input.int(20, title="Length", minval=2, maxval=200),
    multiplier: float = input.float(2.0, title="Deviation multiplier", minval=0.1, maxval=5.0),
):
    builtin_middle, builtin_upper, builtin_lower = ta.bb(close, length, multiplier)
    own_middle, own_upper, own_lower = custom_bands(close, length, multiplier)
    plot(builtin_upper, "Built-in upper", color=color.blue, linewidth=3)
    plot(own_upper, "Custom upper", color=color.orange)
    plot(own_middle, "Custom middle", color=color.green)
    plot(own_lower, "Custom lower", color=color.gray)

A tuple[float, float, float] három számból álló eredmény típusát jelöli. A return middle, upper, lower sor adja vissza ezt a hármast, a hívás előtti három változónév pedig külön-külön átveszi az értékeit. A builtin_middle és a builtin_lower értékét most nem rajzoljuk ki, de később ezeket is felhasználhatod az ellenőrzéshez.

Futtasd a demóadaton. A saját és a kész felső határ egymásra esik; a vastagabb kék vonalon a vékony narancssárga halad. Vidd az egérmutatót egy gyertyára, és hasonlítsd össze a Built-in upper és a Custom upper értékét. Húszas hossznál az első tizenkilenc gyertyán még nincs teljes ablak.

A bands_workshop.py saját függvénye mellett futó grafikonon a Built-in upper és a Custom upper értéke azonos; a saját középvonal és alsó szalag is látható.
A kész és a saját felső szalag értéke a kijelölt gyertyán egyaránt 216,85. A két vonal egymásra esik.

A pontosabb ellenőrzéshez a grafikon eszköztárának Open plot output CSV műveletével nyisd meg a kiírt értékeket. Egy saját változatnál ellenőrizd az első érvényes pontot és a későbbi értékeket is. A szemre azonos vonalak között is lehet kis eltérés.

Próbáld ki önállóan: EMA-középvonal

Módosítsd csak a custom_bands középvonalát: SMA helyett EMA-ból készüljön. A szórás számítása és a kész ta.bb hívás maradjon meg összehasonlításnak.

Mielőtt futtatod, jósolj: a saját felső határ végig egybe fog esni a készével? Mi történik egy hirtelen árváltozás után?

A feladat megoldása

A saját függvény első számítási sora:

middle = ta.ema(source, length)

A két határt ettől az EMA-tól mérjük, a ta.stdev számítása változatlan marad. A saját középvonal és a két határ így általában elmozdul az eredeti szalaghoz képest. A két felső érték különbsége megegyezik az EMA és az SMA különbségével, mert ugyanazt a szórást és szorzót adjuk hozzájuk.

Ez már a saját kísérleti változatod. Az eltérő vonal önmagában nem mutatja meg, hogy egy kereskedési szabály jobb eredményt ad-e vele; azt külön stratégiában, azonos adatokon és beállításokkal vizsgálhatod.

Így haladj tovább a könyvtárban

Válassz ki egy indikátort, és írd le saját szavaiddal, milyen adatot kap, mit jegyez meg, és milyen eredményt ad. Kövesd a belső hívásokat addig, amíg a számítás összeáll benned. Az RSI-nél például az rsi → rma → sma útvonalon a különbségektől a simításig és a kezdőértékig jutsz el.

Olvasd el a hiányzó értékekhez, bemelegedéshez és határesetekhez tartozó ágakat is. Ha saját változatot készítesz, először egyetlen részletet módosíts, majd hasonlítsd össze az eredményeit az eredetivel. Így el tudod különíteni a szándékos változást a számítási hibától.

A könyvtár további függvényeinek forrását ugyanezzel a Command- vagy Ctrl-kattintással érheted el. Egy hosszabb indikátorhoz több idő kellhet, de az olvasás módja ugyanaz: bemenetek, megőrzött adatok, számítás, eredmény.