Итоговый проект объединяет изученное: средние, inputs, условия, функцию, приказы и отображение. Цель в том, чтобы каждое решение можно было связать с данными и написанными правилами.

Опишите программу без кода

Стратегия следит за пересечением двух EMA. Третья, длинная SMA проверяет закрытие выше среднего.

Длинный вход требует перехода быстрой EMA выше медленной снизу и закрытия выше трендовой SMA. Порядок длин: быстрая < медленная < трендовая. При нарушении программа не подаёт вход.

Пересечение EMA вниз даёт рыночное закрытие. К входу привязаны стоп и цель в тиках. Используются одна единица, комиссия 0,1 процента и проскальзывание два тика.

Это правила задания, а не основание ожидать прибыль на рынке. Проверяем их на фиксированных учебных данных.

Полная программа

Создайте 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)

Сохраните и запустите. Появятся три средние и зелёные треугольники условий входа. Фактические входы и выходы отмечены сделками стратегии.

На графике final_strategy.py видны быстрая и медленная EMA, трендовая SMA, сигналы входа и исполненные сделки.
Итоговый проект объединяет три средние, включаемый фильтр и правила выхода для входов.

Что считается на каждой свече?

Три средние и два пересечения выполняются до условий, даже когда фильтр затем запрещает вход. Состояния расчётов проходят весь ряд.

lengths_are_ordered проверяет три числа и возвращает логическое значение. Собственного свечного состояния нет. valid_lengths хранит текущий результат для следующих выражений.

trend_passes истинно при выключенном фильтре или закрытии выше SMA. input.bool даёт переключатель. При выключении not use_trend_filter истинно и вторая часть не нужна.

Средняя всё равно вычисляется и рисуется. Переключатель меняет проверку входа, а не видимость линии.

Недопустимое соотношение длин

Отдельные границы требуют лишь длины от 1 до 200. Можно ввести быструю 30, медленную 10, нарушив заданный порядок.

Проверочная функция отдельно оценивает связь. При неверном порядке линии остаются, но entry_signal не станет истинным. Программа не начнёт входить с отношением длин вне задания.

Проверьте напрямую valid_lengths в отладчике. При неверных сохранённых inputs она ложна весь запуск.

Сигнал не всегда даёт новую сделку

Зелёный треугольник обозначает условие входа. Приказ отправляется симулятору. Для исполнения могут требоваться следующая свеча, достаточный капитал и допустимое количество.

При pyramiding=1 существующая длинная позиция может запрещать ещё один вход. Не считайте число сигналов и сделок автоматически равным.

В начале отсутствует трендовая SMA длиной пятьдесят. При включённом фильтре нельзя признать цену выше неё, и условие входа не выполнено.

Проверьте поведение

Начните со значений по умолчанию и проверьте количество, расходы, исполнение в Properties. Ранее сохранённая настройка может переопределять исходник.

Найдите зелёный сигнал. В отладчике или по графику изучите EMA текущей и прошлой свечей. Проверьте пересечение, SMA-фильтр и порядок длин. Все три части должны быть истинными.

Затем найдите связанный вход в Trades. Проверьте свечу и цену исполнения. У выхода определите причину: пересечение, стоп или цель.

Попробуйте предсказуемые случаи

Для проверки меняйте то, чей результат можете предсказать:

ИзменениеЧто проверить?
Равные быстрая и медленная длиныПроверка запрещает новые сигналы входа
Выключенный фильтрПересечение EMA не требует цены выше SMA
Увеличенные стоп и цельДругое расстояние может изменить свечу выхода
Другое количествоЦеновой сигнал тот же, денежный размер может меняться

Делайте одно изменение за запуск. Сначала записывайте ожидание, потом итог. При расхождении найдите первое отличающееся решение в отладчике.

Сравнение результата

Сравнивая фильтр включённым и выключенным, сохраняйте данные, количество, расходы и длины. Смотрите Performance вместе с Trades. Мало сделок или открытая последняя позиция влияют и на итоги.

Для реальных исторических данных задайте период настройки и отдельной проверки ещё до просмотра результатов. Изменив параметры после просмотра проверочного периода, вы включаете и его в настройку.

В начале данных нужно достаточно свечей прогрева. Запуск вырезанного периода заново строит средние и позиции. Поэтому его начало может отличаться от того же участка внутри длинного ряда.

Попробуйте самостоятельно

Добавьте Use crossings exit. При включении сохраняйте выход по пересечению вниз, при выключении закрывают только стоп и цель. Пересечение вниз считайте в обоих случаях.

Решение

В параметры main():

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

Ветвь закрытия:

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

crosses_down = ta.crossunder(...) оставьте до условий. strategy.exit сохраняется в обоих положениях.

Без выхода по пересечению позиция может остаться после пересечения EMA вниз. Новый сигнал возможен, но существующая позиция помешает новой сделке.

Создайте собственный вариант

Скопируйте под новым именем и опишите одно новое правило. Укажите данные и свечу решения, ручную проверку и управляющий input.

Сначала вычислите правило отдельной переменной. Отобразите или проверьте её в отладчике, прежде чем делать условием входа или выхода. Это помогает искать ошибку расчёта до объяснения результата всей стратегии.

Код, сохранённые настройки и данные вместе описывают эксперимент. Сохранив их, можно повторить и проверить запуск.