Блог им. empenoso
Одна из главных проблем стандартных скользящих средних (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/
Потом выкинул этот файл, когда проштудировал теорвер, стилизованные эмпирические факты, RandomWalk-1,2,3 и другие наработки.
99,9% индикаторов — это трансформационные КИХ-фильтры или БИХ-фильтры с очень посредственными АЧХ и ФЧХ.
Искать надо в другом направлении.
лайфхак — если вы не можете изолированно протестировать условие своего выхода, значит его логика подогнана под логику входа. Да-да бывает даже такая форма подгонки. их вообще много этих самых подгонок, не только параметрическая под конкретные данные, которая на самом слуху...
еще — проблема в том, что свойства биржевых рядов неэргодичные. это создает особую сложность при интерпретации результатов тестирования.
Сергей Сергаев, вы имеете ввиду что если стратегия показывала хорошие результаты на исторических данных, это не означает, что она будет работать в будущем.
Но я-то думал что единственное слово будет что-то вроде «фундаментал».
торговая стратегия — это очень широкое определение, которое в т.ч. включает в себя множество обратных связей, которые позволяют стратегии не только понимать что происходит на рынке, но и понимать что происходит внутри стратегии, какие процессы, с какой частотой, какой отдачей и др.
а что ты нашел?
15ma на часовом таймфрейме равна 900ma на минутном таймфрейме. Проблема решена)
1. Скользяшки не базис, поскольку есть системы, в которых вообще не используются скользяшки.
2. У А.Г., если взять публичный пример, используются условные приказы. Например, если цена пробьет такой-то уровень, купить или продать. Эти уровни вычисляется наперед и иногда корректируются. То есть нет прогноза ровно на час или ровно на день.
Фишка в том, что стандартные критерии оптимальности для фильтров, что ВЧ, что НЧ, что Гильберта не подходят для ценовых рядов. Поэтому приходится эмпирически выбирать тот или иной фильтр. Я не нашел преимуществ стандартных фильтров перед экспоненциальной скользяшкой. Да и адаптивные скользяшки не порадовали, тут я согласен.
Сергей Сергаев, я в настоящее время использую скользящую среднюю как базис, потому что есть одно фундаментальное правило — цена не может бесконечно долго отклоняться от скользящей средней, и из этого правила можно черпать закономерности связи двух параметров цена-время, второе правило цена не может бесконечно далеко уйти от скользящей средней, поэтому можно находить закономерности и оценивать в какой стадии тренда сейчас находиться инструмент, отличие боковика от тренда — степень отклонения от своей средней цены.
конечно цена будет крутиться вокруг скользящей, просто по причине среза высоких частот цены в ее скользящей.
можно бесконечно долго смотреть на 3 вещи:
1. как горит огонь
2. как течет вода
3. как цена пересекает скользящую то сверху то снизу
))
а реальный график содержит память в ценах, вот ее-то и надо научиться извлекать.
Если тестируется индикатор, так надо на всех ликвидных в одном и том же временном окне его и бэктестить.
А если тестируется выбор сильнейшей акции по типу моментума, но на новом индикаторе, тоже проще было бы прогнать на фиксированном наборе акций, тем более, что список ликвидных за последние 5 лет не сильно изменился.
Для фьючерсов сделал что-то похожее только без библиотеки aiomoex.
Всё таки проще без неё похоже работать напрямую с API Мосбиржи