Projekt łączy w pliku poznane części: średnie, wejścia, warunki, własną funkcję, zlecenia i wyświetlanie. Celem jest program, którego każdą decyzję potrafisz powiązać z danymi i opisanymi regułami.

Opisz program bez kodu

Strategia obserwuje przecięcia dwóch EMA. Trzecia, dłuższa SMA sprawdza, czy zamknięcie znajduje się nad średnią.

Warunkiem longa jest przejście szybkiej EMA nad wolną i zamknięcie nad SMA trendu. Długości mają zachować kolejność szybka < wolna < trend. Inaczej program nie składa wejścia.

Przecięcie EMA w dół daje zamknięcie rynkowe. Do wejścia należą też stop i cel w tickach. Symulacja używa stałej jednej jednostki, prowizji 0,1 procent i dwóch ticków poślizgu.

To reguły zadania. Nie wnioskujemy z nich o zyskowności na jakimś rynku. Do kontroli używamy stałych danych demonstracyjnych książki.

Pełny program

Utwórz final_strategy.py:

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


def lengths_are_ordered(fast: int, slow: int, trend: int) -> bool:
    return fast < slow and slow < trend


@script.strategy(
    "Filtered Crossing Strategy",
    overlay=True,
    initial_capital=10000,
    default_qty_type=strategy.fixed,
    default_qty_value=1,
    pyramiding=1,
    commission_type=strategy.commission.percent,
    commission_value=0.1,
    slippage=2,
)
def main(
    fast_length: int = input.int(9, title="Fast EMA length", minval=1, maxval=200),
    slow_length: int = input.int(21, title="Slow EMA length", minval=1, maxval=200),
    trend_length: int = input.int(50, title="Trend SMA length", minval=1, maxval=200),
    use_trend_filter: bool = input.bool(True, title="Use trend filter"),
    stop_ticks: int = input.int(100, title="Stop distance (ticks)", minval=1),
    target_ticks: int = input.int(200, title="Target distance (ticks)", minval=1),
):
    fast_average = ta.ema(close, fast_length)
    slow_average = ta.ema(close, slow_length)
    trend_average = ta.sma(close, trend_length)
    crosses_up = ta.crossover(fast_average, slow_average)
    crosses_down = ta.crossunder(fast_average, slow_average)
    valid_lengths = lengths_are_ordered(fast_length, slow_length, trend_length)
    trend_passes = not use_trend_filter or close > trend_average
    entry_signal = valid_lengths and crosses_up and trend_passes
    if entry_signal:
        strategy.entry("Long", strategy.long)
    if crosses_down:
        strategy.close("Long")
    strategy.exit("Bracket", "Long", loss=stop_ticks, profit=target_ticks)
    plot(fast_average, "Fast EMA", color=color.blue, linewidth=2)
    plot(slow_average, "Slow EMA", color=color.orange, linewidth=2)
    plot(trend_average, "Trend SMA", color=color.gray, linewidth=2)
    plotshape(entry_signal, "Entry signal", style=shape.triangleup,
              location=location.belowbar, color=color.green)

Zapisz i uruchom na demonstracji. Pojawią się trzy średnie i zielone trójkąty warunku wejścia. Rzeczywiste wejścia i wyjścia pokazują znaczniki transakcji strategii.

Wykres final_strategy.py pokazuje szybką i wolną EMA, SMA trendu, sygnały wejścia i zrealizowane transakcje.
Projekt końcowy używa trzech średnich, przełączanego filtra i reguł wyjścia przypisanych wejściom.

Co liczymy na każdej świecy?

Trzy średnie i dwa przecięcia wykonują się przed warunkami. Liczymy je nawet wtedy, gdy filtr nie pozwala wejść, by stan przechodził przez cały szereg.

Własne lengths_are_ordered bada trzy liczby i zwraca logikę. Nie ma stanu świec. valid_lengths zachowuje obecny wynik dla dalszych wyrażeń.

trend_passes jest prawdziwe przy wyłączonym filtrze albo zamknięciu nad SMA. input.bool daje przełączane wejście logiczne. not use_trend_filter jest prawdą przy wyłączonym filtrze, więc drugi człon nie jest wtedy potrzebny.

Wyłączając filtr, nadal liczymy i rysujemy średnią. Przełącznik zmienia sprawdzenie wejścia, nie widoczność linii.

Nieprawidłowa relacja długości

Osobne granice mówią tylko, że każda długość ma leżeć między 1 a 200. Nadal można podać szybką 30 i wolną 10, naruszając kolejność zadania.

Funkcja osobno sprawdza związek. Przy złej kolejności linie są rysowane, lecz entry_signal nie może być prawdziwe. Program nie otwiera więc pozycji z niezgodną relacją długości.

W debuggerze sprawdzisz to przez valid_lengths. Przy niepoprawnych zapisanych wejściach pozostanie fałszywe przez cały przebieg.

Sygnał nie zawsze tworzy transakcję

Trójkąt oznacza warunek wejścia. Strategia przekazuje zlecenie symulatorowi. Realizacja może wymagać kolejnej świecy, wystarczającego kapitału i dozwolonej ilości.

Przy pyramiding=1 symulator może nie dopuścić następnego longa, gdy pozycja już istnieje. Nie zakładaj więc równej liczby sygnałów i transakcji.

Na początku brakuje SMA filtra o długości pięćdziesiąt. Przy włączonym filtrze nie można wtedy uznać ceny za leżącą nad nią, więc warunek wejścia nie przechodzi.

Sprawdź działanie

Zacznij od domyślnych wartości i sprawdź w Properties ilość, koszty i realizację. Starsze nadpisanie może zmienić ustawienia źródła.

Znajdź zielony sygnał. W debuggerze lub danych wykresu obejrzyj obie EMA na obecnej i poprzedniej świecy. Sprawdź przecięcie, filtr SMA i kolejność długości. Wszystkie trzy części muszą być prawdziwe.

Następnie znajdź powiązane wejście w Trades. Sprawdź świecę i cenę realizacji. Przy wyjściu ustal, czy wyjaśnia je przecięcie, stop czy cel.

Wypróbuj przewidywalne przypadki

Do kontroli wybieraj zmiany o przewidywalnym skutku:

ZmianaCo sprawdzić?
Równa szybka i wolna długośćKontrola długości blokuje nowe sygnały wejścia
Filtr trendu wyłączonyPrzecięcie EMA nie wymaga ceny nad SMA
Większy stop i celInna odległość może zmienić świecę wyjścia
Zmieniona ilośćSygnał cenowy ten sam, wynik pieniężny może się różnić

W jednym przebiegu zmieniaj tylko jeden punkt. Zapisuj najpierw przewidywanie, potem wynik. Przy rozbieżności szukaj w debuggerze pierwszej odmiennej decyzji.

Porównanie wyników

Porównując włączony i wyłączony filtr, zachowaj te same dane, ilość, koszty i długości. Oglądaj Performance razem z Trades. Mała liczba transakcji lub otwarta ostatnia pozycja wpływają na podsumowanie.

Na prawdziwej historii określ okres strojenia i osobnej kontroli jeszcze przed obejrzeniem wyników. Zmieniając parametry po obejrzeniu okresu kontrolnego, włączasz go do strojenia.

Początek danych musi zapewnić dostateczną rozgrzewkę. Oddzielne uruchomienie wyciętego okresu odbudowuje średnie i pozycję od nowa. Jego początek może więc różnić się od tego samego okresu w środku dłuższej serii.

Spróbuj samodzielnie

Dodaj przełącznik Use crossings exit. Włączony zachowuje zamknięcie na przecięciu w dół, wyłączony pozostawia tylko stop i cel. Przecięcie obliczaj w obu trybach.

Rozwiązanie zadania

Do parametrów main():

use_crossing_exit: bool = input.bool(True, title="Use crossings exit"),

Gałąź zamknięcia:

if use_crossing_exit and crosses_down:
    strategy.close("Long")

crosses_down = ta.crossunder(...) pozostaje przed warunkami, a strategy.exit w obu położeniach przełącznika.

Przy wyłączonym zamknięciu przez przecięcie pozycja może pozostać otwarta po przecięciu EMA w dół. Nowy sygnał wejścia może się pojawić, choć istniejąca pozycja nie pozwoli otworzyć transakcji.

Stwórz własną wersję

Skopiuj program pod nową nazwą i opisz jedną nową regułę. Określ dane i świecę decyzji, sposób ręcznego sprawdzenia i wejście sterujące regułą.

Najpierw oblicz ją w osobnej zmiennej. Narysuj lub obejrzyj w debuggerze, zanim użyjesz jako warunku wejścia czy wyjścia. Błąd można wtedy znaleźć w obliczeniu, zanim zaczniesz tłumaczyć wynik całej strategii.

Źródło, zapisane ustawienia i dane wspólnie opisują eksperyment. Zachowując je, możesz odtworzyć i sprawdzić przebieg.