Одна из главных проблем стандартных скользящих средних (SMA) — это запаздывание. Поскольку SMA рассчитывается как среднее за определенный период, её значение всегда отстает от реальной цены, что мешает своевременному входу в сделку.
Джон Элерс предложил решение — прогнозируемая скользящая средняя (PMA, Projected Moving Average). В отличие от обычных скользящих, PMA использует линейную регрессию для прогнозирования будущих значений, уменьшая лаг.
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 помогают находить точки входа и выхода, делая стратегию более адаптивной к изменениям рынка.
S&P 500 E-Mini Futures
Вход в длинную позицию:
– Цена закрытия на недельном графике выше 50-недельной PMA.
– Цена закрытия на дневном графике выше 50-дневной PMA.
– 10-дневная PMA выше 50-дневной.
Риск-менеджмент:
– Первоначальный стоп-лосс устанавливается на 10% ниже цены входа.
– Выход из позиции осуществляется по скользящему стопу на основе ATR.
Весь код представлен на GitHub.
Модуль data_loader.py
Для тестирования стратегии необходимо загружать актуальные данные о торгах.
В этом помогает библиотека aiomoex
, которая предоставляет API-доступ к Московской бирже. В модуле data_loader.py
реализована функция fetch_moex_data
, позволяющая асинхронно получать исторические данные по свечам.
Функция запрашивает данные за последние 1825 дней (примерно 5 лет) и конвертирует их в формат Pandas DataFrame. Особенность реализации — использование асинхронного HTTP-клиента aiohttp, что ускоряет загрузку. Данные приводятся к удобному формату: преобразуются даты, устанавливается индекс, а названия колонок заменяются на стандартные для анализа.
Фильтрация ликвидных бумаг для тестирования. Модуль scanner.py
После загрузки данных важно отобрать ликвидные бумаги. Для этого в модуле scanner.py
реализована функция get_top_20_stocks, которая анализирует объем торгов за последние 14 дней и выделяет 20 наиболее ликвидных акций.
Алгоритм работы следующий:
Таким образом, отбираются бумаги с высоким оборотом, что повышает надежность тестирования стратегии и снижает риск торговли неликвидными активами.
Зачем всё разделил на модули?
Разделение кода на модули делает его более удобным для сопровождения, масштабирования и переиспользования. В нашем случае:
Такой подход позволяет независимо модифицировать и тестировать каждый компонент системы.
Пример кода для бэктестинга с использованием 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):
Результаты бэктеста:
Стратегия: 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):
Результаты бэктеста:
Стратегия: 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 г.
backtesting.py => backtesting.ru?
Хоббит, это ведь не ссылка… так библиотека называется
Вот ссылка: https://kernc.github.io/backtesting.py/
15ma на часовом таймфрейме равна 900ma на минутном таймфрейме. Проблема решена)
1. Скользяшки не базис, поскольку есть системы, в которых вообще не используются скользяшки.
2. У А.Г., если взять публичный пример, используются условные приказы. Например, если цена пробьет такой-то уровень, купить или продать. Эти уровни вычисляется наперед и иногда корректируются. То есть нет прогноза ровно на час или ровно на день.
Если тестируется индикатор, так надо на всех ликвидных в одном и том же временном окне его и бэктестить.
А если тестируется выбор сильнейшей акции по типу моментума, но на новом индикаторе, тоже проще было бы прогнать на фиксированном наборе акций, тем более, что список ликвидных за последние 5 лет не сильно изменился.
Для фьючерсов сделал что-то похожее только без библиотеки aiomoex.
Всё таки проще без неё похоже работать напрямую с API Мосбиржи