Одна из главных проблем стандартных скользящих средних (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?
15ma на часовом таймфрейме равна 900ma на минутном таймфрейме. Проблема решена)
Сергей Сергаев, вы имеете ввиду что если стратегия показывала хорошие результаты на исторических данных, это не означает, что она будет работать в будущем.
Но я-то думал что единственное слово будет что-то вроде «фундаментал».