Блог им. empenoso

Тестирование торговой стратегии с использованием нового индикатора Джона Ф. Элерса на Python для дневных данных Московской биржи

Торговля акциями требует гибкости, особенно когда речь идет о тестировании стратегий технического анализа на прошлых данных. Я выбрал Python и библиотеки backtesting.py и aiomoex, потому что они позволяют анализировать рынок без сложных платформ и ограничений. Python дает свободу автоматизации, backtesting.py обеспечивает удобный и быстрый механизм тестирования стратегий, а aiomoex позволяет скачивать данные напрямую с Московской биржи без привязки к брокеру.

Важно, что backtesting.py получил обновление после четырех лет без обновлений, что делает его актуальным инструментом. И в отличие от MetaTrader, StockSharp, TSLab и Quik, которые работают с Московской биржей, но требуют Windows, если брокер имеет API, то можно запускать скрипт на любом сервере, включая облачные решения и Raspberry Pi.

В этой статье я протестирую самую свежую стратегию теханализа Джона Ф. Элерса (John Ehlers), направленную на устранение запаздывания скользящей средней. Разберемся, как её адаптировать к акциям Московской биржи и протестировать с помощью Python.

Новый индикатор Джона Элерса «устранение запаздывания скользящей средней»

Одна из главных проблем стандартных скользящих средних (SMA) — это запаздывание. Поскольку SMA рассчитывается как среднее за определенный период, её значение всегда отстает от реальной цены, что мешает своевременному входу в сделку.

Джон Элерс предложил решение — прогнозируемая скользящая средняя (PMA, Projected Moving Average). В отличие от обычных скользящих, PMA использует линейную регрессию для прогнозирования будущих значений, уменьшая лаг.

Тестирование торговой стратегии с использованием нового индикатора Джона Ф. Элерса на Python для дневных данных Московской биржи

SBER

Формула

PMA:<code>PMA = SMA + Slope * Length / 2,

<br />где Slope — наклон линии регрессии.

Дополнительно Элерс предложил прогнозировать саму PMA:<br /><code>PredictPMA = PMA + 0.5 (Slope - Slope[2]) Length


и наклон:<br /><code class="inline-code">PredictSlope = 1.5 Slope - 0.5 Slope[4].

Пересечения PredictPMA и PMA помогают находить точки входа и выхода, делая стратегию более адаптивной к изменениям рынка.

Тестирование торговой стратегии с использованием нового индикатора Джона Ф. Элерса на Python для дневных данных Московской биржи

S&P 500 E-Mini Futures

Стратегия на основе индикатора PMA Джона Ф. Элерса

Вход в длинную позицию:

– Цена закрытия на недельном графике выше 50-недельной PMA.
– Цена закрытия на дневном графике выше 50-дневной PMA.
– 10-дневная PMA выше 50-дневной.

Риск-менеджмент:

– Первоначальный стоп-лосс устанавливается на 10% ниже цены входа.
– Выход из позиции осуществляется по скользящему стопу на основе ATR.

Реализация бэктестинга через backtesting.py. Определение топ-20 акций по объему

Весь код представлен на GitHub.

Модуль data_loader.py

Тестирование торговой стратегии с использованием нового индикатора Джона Ф. Элерса на Python для дневных данных Московской биржи

Для тестирования стратегии необходимо загружать актуальные данные о торгах.

В этом помогает библиотека aiomoex, которая предоставляет API-доступ к Московской бирже. В модуле data_loader.py реализована функция fetch_moex_data, позволяющая асинхронно получать исторические данные по свечам.

Функция запрашивает данные за последние 1825 дней (примерно 5 лет) и конвертирует их в формат Pandas DataFrame. Особенность реализации — использование асинхронного HTTP-клиента aiohttp, что ускоряет загрузку. Данные приводятся к удобному формату: преобразуются даты, устанавливается индекс, а названия колонок заменяются на стандартные для анализа.

Фильтрация ликвидных бумаг для тестирования. Модуль scanner.py

Тестирование торговой стратегии с использованием нового индикатора Джона Ф. Элерса на Python для дневных данных Московской биржи

После загрузки данных важно отобрать ликвидные бумаги. Для этого в модуле scanner.py реализована функция get_top_20_stocks, которая анализирует объем торгов за последние 14 дней и выделяет 20 наиболее ликвидных акций.

Алгоритм работы следующий:

  1. Получение списка всех торгуемых акций на основном рынке (TQBR) через API Московской биржи.
  2. Асинхронная загрузка дневных данных по каждому инструменту с помощью fetch_moex_data.
  3. Расчет суммарного объема торгов за 14 дней.
  4. Формирование списка из 20 акций с наибольшим объемом.

Таким образом, отбираются бумаги с высоким оборотом, что повышает надежность тестирования стратегии и снижает риск торговли неликвидными активами.

Реализация бэктестинга через backtesting.py. Тестирование стратегии на исторических данных

Зачем всё разделил на модули?

Разделение кода на модули делает его более удобным для сопровождения, масштабирования и переиспользования. В нашем случае:

  • data_loader.py отвечает за загрузку данных с Московской биржи.
  • scanner.py фильтрует ликвидные бумаги.
  • backtester.py выполняет бэктестинг.
  • strategy.py содержит описание стратегии.
  • main.py запускает сканирование и тестирование

Такой подход позволяет независимо модифицировать и тестировать каждый компонент системы.

Пример кода для бэктестинга с использованием backtesting.py

<code>import asyncio
import pandas as pd
from backtesting import Backtest
from data_loader import fetch_moex_data
from strategy import LongOnlyPMAMultiTimeframeATRTrailingStop

async def run_backtest(ticker):
    print(f"\n{'='*50}")
    print(f"🚀 Запуск бэктеста для {ticker}")
    print(f"{'='*50}\n")

    # Получаем данные
    df, start_str, end_str = await fetch_moex_data(ticker) # Получаем start_str и end_str

    # Запускаем бэктест
    print("⏳ Запуск бэктеста...")
    strategy_class = LongOnlyPMAMultiTimeframeATRTrailingStop # Класс стратегии остается прежним
    strategy_name = f"{ticker}_{start_str}_{end_str}_LongOnlyPMAMultiTimeframeATRTrailingStop" # Динамическое имя
    DynamicStrategyClass = type(strategy_name, (strategy_class,), {}) # Создаем динамический класс стратегии
    bt = Backtest(df, DynamicStrategyClass, cash=100_000, commission=0.002) # Используем динамический класс
    stats = bt.run()

    # Вывод результатов
    print("\n📊 Результаты бэктеста:")
    print(f"⚙️ Стратегия: {strategy_name}") # Выводим динамическое имя стратегии
    print(f"📅 Период тестирования: с {stats['Start']} по {stats['End']}")
    print(f"💰 Начальный капитал: 100,000 руб.")
    print(f"💵 Конечный капитал: {stats['Equity Final [$]']:.2f} руб.")
    print(f"📈 Общая доходность: {stats['Return [%]']:.2f}%")
    print(f"📊 Годовая доходность: {stats['Return (Ann.) [%]']:.2f}%") 
    print(f"📈 Коэффициент Шарпа: {stats['Sharpe Ratio']:.2f}") 
    print(f"📉 Максимальная просадка: {stats['Max. Drawdown [%]']:.2f}%")
    print(f"🔄 Количество сделок: {stats['# Trades']}")
    print(f"✅ Процент выигрышных сделок: {stats['Win Rate [%]']:.2f}%")
    print(f"💪 Лучшая сделка: +{stats['Best Trade [%]']:.2f}%")
    print(f"🙁 Худшая сделка: {stats['Worst Trade [%]']:.2f}%")
    print(f"⏱️ Средняя продолжительность сделки: {stats['Avg. Trade Duration']}")
    # Построение графика
    print("\n📊 Построение графика результатов...")
    try:
        bt.plot()
        print("✅ График успешно построен!")
    except ValueError as e:
        print(f"❌ Ошибка при построении графика: {e}")
    print(f"\n{'='*50}")
    print(f"🏁 Бэктест для {ticker} завершен")
    print(f"{'='*50}\n")
    return stats</code>

Основные метрики оценки

Для оценки стратегии используются ключевые метрики:

  • Общая доходность (%) — показывает, сколько стратегия заработала за весь период тестирования.
  • Годовая доходность (%) — усреднённый годовой прирост капитала.
  • Коэффициент Шарпа — измеряет соотношение доходности к риску.
  • Максимальная просадка (%) — определяет максимальную потерю капитала.
  • Процент выигрышных сделок.
  • Средняя продолжительность сделки.

Эти показатели позволяют оценить эффективность стратегии и принять решение о её использовании в реальной торговле.

Результаты тестирования на акциях Московской биржи

Как положительные, так и отрицательные.

Примеры в html файлах на GitHub'е.

СПБ Биржа (тикер SPBE):

Тестирование торговой стратегии с использованием нового индикатора Джона Ф. Элерса на Python для дневных данных Московской биржи

Результаты бэктеста:

Стратегия: SPBE_2020-03-03_2025-03-02_LongOnlyPMAMultiTimeframeATRTrailingStop

Период тестирования: с 2021-11-19 00:00:00 по 2025-03-01 00:00:00

Начальный капитал: 100,000 руб.

Конечный капитал: 349138.97 руб.

Общая доходность: 249.14%

Годовая доходность: 47.34%

Коэффициент Шарпа: 0.80

Максимальная просадка: -27.41%

Количество сделок: 11

Процент выигрышных сделок: 54.55%

Лучшая сделка: +36.56%

Худшая сделка: -8.31%

Средняя продолжительность сделки: 25 days 00:00:00

Новатэк ао (тикер NVTK):

Тестирование торговой стратегии с использованием нового индикатора Джона Ф. Элерса на Python для дневных данных Московской биржи

Результаты бэктеста:

Стратегия: NVTK_2020-03-03_2025-03-02_LongOnlyPMAMultiTimeframeATRTrailingStop

Период тестирования: с 2020-03-03 00:00:00 по 2025-03-01 00:00:00

Начальный капитал: 100,000 руб.

Конечный капитал: 94443.56 руб.

Общая доходность: -5.56%

Годовая доходность: -1.15%

Коэффициент Шарпа: -0.07

Максимальная просадка: -38.70%

Количество сделок: 22

Процент выигрышных сделок: 27.27%

Лучшая сделка: +18.55%

Худшая сделка: -10.78%

Средняя продолжительность сделки: 23 days 00:00:00

Проблема учета смены лидеров по объему

Мой код отдельно тестирует каждую акцию из топ-20 на момент отбора (на сегодня). Однако он не учитывает смену лидеров по объему и не позволяет работать с единой корзиной акций, где позиции могут удерживаться даже после выпадения бумаги из топ-20. Это важно, потому что иначе стратегия теряет контекст уже открытых сделок.

Решение — создание скользящего портфеля, учитывающего смену лидеров — это стратегия, при которой состав портфеля регулярно пересматривается и обновляется на основе новых данных.

Следующие шаги

Фиксированный список топ-акций по объему устаревает. Использование динамического реестра позволит оперативно учитывать смену лидеров, корректируя состав активных позиций в стратегии.

Библиотека ta-lib мне не очень понравилась из-за сложностей с установкой — проще переписать индикатор вручную в будущем.

Получится ли реализовать это через backtesting.py? Скорее всего вряд ли.

Скорее всего придётся вернутся к Backtrader.

Заключение

Тестирование стратегии на акциях Мосбиржи показало её стабильную эффективность при использовании индикатора PMA на дневных свечах.

Python доказал свою ценность в алгоритмической торговле, обеспечивая гибкость и автоматизацию. Однако backtesting.py имеет ограничения.

Автор: Михаил Шардин

🔗 Моя онлайн-визитка

📢 Telegram «Умный Дом Инвестора»

11 марта 2025 г.

★13
#16 по плюсам, #14 по комментариям
29 комментариев
Не работают
backtesting.py => backtesting.ru?
avatar

Хоббит, это ведь не ссылка… так библиотека называется

Вот ссылка: https://kernc.github.io/backtesting.py/

Я с 2002 по 2008 год собирал все эти хитрые индикаторы и получил файл более 200 страниц с формулами, логикой входов-выходов, фильтрами. Там и AMA Кауфмана, Видиа Тушара Чанда, Optimal Tracking Filter и FRAMA Эйлерса. Вообще у Эйлерса много всяких адаптивных индикаторов.
 
Потом выкинул этот файл, когда проштудировал теорвер, стилизованные эмпирические факты, RandomWalk-1,2,3 и другие наработки.
99,9% индикаторов — это трансформационные КИХ-фильтры или БИХ-фильтры с очень посредственными АЧХ и ФЧХ.
 
Искать надо в другом направлении.
Сергей Сергаев, например в каком направлении? Одним словом
Михаил Шардин, слов там много. но например присутствует сочетание «раздельное тестирование входов и выходов».
лайфхак — если вы не можете изолированно протестировать условие своего выхода, значит его логика подогнана под логику входа. Да-да бывает даже такая форма подгонки. их вообще много этих самых подгонок, не только параметрическая под конкретные данные, которая на самом слуху...
 
еще — проблема в том, что свойства биржевых рядов неэргодичные. это создает особую сложность при интерпретации результатов тестирования.

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

Но я-то думал что единственное слово будет что-то вроде «фундаментал».

 

Михаил Шардин, что понимать под «стратегией». если это пересечение скользящих на графике — то это не стратегия, а всего лишь пара параметрических условий.
торговая стратегия — это очень широкое определение, которое в т.ч. включает в себя множество обратных связей, которые позволяют стратегии не только понимать что происходит на рынке, но и понимать что происходит внутри стратегии, какие процессы, с какой частотой, какой отдачей и др.
Сергей Сергаев, просто общие слова
Сергей Сергаев, ты нашел?)
avatar
RiskTrader, если оглянуться назад, то я прошел довольно далеко, и как бывший сотрудник инвесткомпании (начальник брокерского направления) уже годы не хожу на работу )
а что ты нашел?
Сергей Сергаев, в процессе)
avatar
Решение запаздывания скользящий средней решается уменьшением таймфрейма, у Элиота в то время этой возможности не было, у нас есть.
15ma на часовом таймфрейме равна 900ma на минутном таймфрейме. Проблема решена)
Никита Шляпников, зачем 15 минут. Сама цена и есть неотстающий индикатор. 
avatar
SergeyJu, 15ma часового таймфрейма -> 15 moving avarange for timeframe 1 hours. Скользящие средние базис всех индикаторов и служит для принятия решения. Проблема большинства индикаторов строящихся на цене состоит в том что заранее не известно как закроется временной период (например часовик, при этом волатильность в течение часа может быть существенной) поэтому индикаторы на бектесте работают отлично, а в реальности ужасно, из-за эффекта запаздывания и несвоевременно принятого решения.
Никита Шляпников, 
1. Скользяшки не базис, поскольку есть системы,  в которых вообще не используются скользяшки. 
2. У А.Г., если взять публичный пример,  используются условные приказы. Например, если цена пробьет такой-то уровень, купить или продать. Эти уровни вычисляется наперед  и иногда корректируются. То есть нет прогноза ровно на час или ровно на день. 
avatar
Никита Шляпников, скользящие средние — это ФНЧ, причем с абсолютно негодными АЧХ и ФЧХ. более того, наложение ФНЧ на неэргодичный ценовой ряд — это полная профанация. не используйте скользящие или ужмите их до логических конструкций — может быть WMA где-то еще подойдет если наблюдается автокорреляция, или взвешенная по объему скользящая — хотя бы учтет аспект рыночной активности на том или ином участке…
Сергей Сергаев, неэргодичные ряды бывают разные. Также как и нестационарные. 
Фишка в том, что стандартные критерии оптимальности для фильтров, что ВЧ, что НЧ, что Гильберта не подходят для ценовых рядов. Поэтому приходится эмпирически  выбирать тот или иной фильтр. Я не нашел преимуществ стандартных фильтров перед экспоненциальной скользяшкой. Да и адаптивные скользяшки не порадовали, тут я согласен.
avatar

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

Никита Шляпников, скользящая и цена это два взаимосвязанных объекта )
конечно цена будет крутиться вокруг скользящей, просто по причине среза высоких частот цены в ее скользящей.
можно бесконечно долго смотреть на 3 вещи:
1. как горит огонь
2. как течет вода
3. как цена пересекает скользящую то сверху то снизу
))
Никита Шляпников, если построить случайный график генератором rnd() и наложить на него скользящую среднюю, то на таком графике так же будут участки роста/падения, и эта «цена» будет пересекать скользящую то снизу то сверху. но у этого случайного графика в данных не будет памяти.
а реальный график содержит память в ценах, вот ее-то и надо научиться извлекать.
Сергей Сергаев, биржа все таки не рандом, тут есть пределы волатильности и профессиональные участники которые контролируют движение цены
Никита Шляпников, Ваш подход вполне имеет право на жизнь. Только он далеко не единственный. 
avatar
 Не понял методологический посыл статьи.
Если тестируется индикатор, так надо на всех ликвидных в одном и том же временном окне его и бэктестить. 
А если тестируется выбор сильнейшей акции по типу моментума, но на новом индикаторе, тоже проще было бы прогнать на фиксированном наборе акций, тем более, что список ликвидных за последние 5 лет не сильно изменился.
avatar
SergeyJu, в статье нет методологического посыла, поскольку ее писавший на начальной ступени находится.
Спасибо за такой качественный и подробный пример. Сам потихоньку копаюсь в Python, ваш код мне будет очень полезен для дальнейшего погружения в эту тему.
avatar
OnlyHuman, на гитхабе выложил. Ещё туда залью — пока остановился немного.
Для фьючерсов сделал что-то похожее только без библиотеки aiomoex.
Всё таки проще без неё похоже работать напрямую с API Мосбиржи
Если стоит Python в сборке от Anaconda, то установка ta-lib очень простая.
avatar
Кстати, а угол наклона считается по ценам или по самой скользяшке и как связаны временное окно расчета линейной регрессии и параметр скользяшки. 
avatar
И в отличие от MetaTrader, StockSharp, TSLab и Quik, которые работают с Московской биржей, но требуют Windows, если брокер имеет API, то можно запускать скрипт на любом сервере, включая облачные решения и Raspberry Pi.
Не уловил в чем именно торговое преимущество.

теги блога Михаил Шардин

....все тэги



UPDONW
Новый дизайн