Хотя Python считается слишком медленным языком для высокочастотного трейдинга, он остается главным инструментом квантов для исследования рынка и бэктестинга стратегий. Создание прибыльного алгоритма — это постоянная борьба с ловушками вроде заглядывания в будущее, ошибки выжившего и хаотичного шума в соцсетях. Практическое руководство по алготрейдингу показывает, как объединить кластеризацию K-Means, модель волатильности GARCH(1,3) и теорию Марковица в устойчивую систему, способную стабильно обходить рыночные бенчмарки.
📈 Введение в алгоритмическую торговлю: от основ Python до машинного обучения 2:37
Основы алготрейдинга и роль Python: почему язык программирования решает всё 2:37
Алгоритмическая торговля представляет собой процесс исполнения сделок на основе заранее определенных правил, объединенных в единую систему или стратегию . Эти правила описываются с помощью языка программирования и исполняются компьютером без необходимости постоянного ручного контроля . На практике алготрейдинг разделяют на два ключевых подхода:
-
Мануальный (полуавтоматический) режим: когда система генерирует скринеры акций или отправляет алерты при выполнении условий, но финальное решение и ручное исполнение ордера остаются за человеком .
-
Полностью автоматический режим: когда сложный комплекс алгоритмов самостоятельно рассчитывает размер позиции, определяет точки входа и выхода, после чего автоматически отправляет заявки брокеру .
Python прочно закрепил за собой статус ключевого инструмента в количественных финансах и анализе данных . Его популярность обусловлена огромной экосистемой библиотек и простотой синтаксиса, значительно ускоряющей проведение исследований . В количественных финансах Python применяется для построения конвейеров данных, тестирования гипотез (бэктестинга) и автоматизации несложных систем . Тем не менее, у языка есть ограничение — низкая скорость выполнения кода . Для высокочастотных стратегий (HFT) или ультрасложных инфраструктур, где критически важна микросекундная задержка, разработчики выбирают C++ или Java .
Индустрия количественных финансов предлагает колоссальные карьерные возможности в хедж-фондах, банках и проп-трейдинговых фирмах . По последним оценкам, средняя базовая зарплата количественного исследователя (Quant Researcher) составляет около 173 000 долларов в год, не включая ежегодные бонусы . Для успешного старта в этой сфере специалисту необходимо глубоко понимать Python, уметь воспроизводить академические финансовые работы на практике, проводить точный бэктестинг и владеть методами машинного обучения .
Машинное обучение в количественных стратегиях: возможности и суровые реалии рынка 4:49
Интеграция машинного обучения (ML) открывает новые горизонты для создания торговых стратегий. В рамках обучения с учителем (Supervised Learning) ML-модели эффективно применяются для генерации сигналов, например, прогнозирования направления движения цены или знака доходности актива [4:49, 5:03]. Также модели полезны в риск-менеджменте для точного определения размера позиций, весов активов в портфеле или расчета уровней стоп-лосса . Обучение без учителя (Unsupervised Learning) помогает извлекать скрытые закономерности, выявлять рыночные режимы, проводить кластеризацию и оптимизировать структуру портфелей .
Однако применение ML на финансовых рынках сопряжено с серьезными барьерами, главным из которых является теоретический парадокс — «петля обратной связи рефлексивности» . Суть явления в том, что рынок адаптируется к действиям участников. Если модель находит устойчивый паттерн — например, что определенная акция регулярно растет по пятницам — трейдер начинает покупать ее в четверг, чтобы зафиксировать прибыль в пятницу . По мере того как другие участники рынка обнаруживают эту же закономерность и начинают действовать аналогично, точка роста смещается на четверг, а изначальная рыночная неэффективность полностью арбитражируется и исчезает .
Сложность прогнозирования различных величин на рынке также неоднородна. Труднее всего предсказывать абсолютные цены и доходности активов . Немного проще, но все еще крайне сложно прогнозировать направление движения цены (знак доходности) или макроэкономические показатели вроде еженедельных заявок на пособие по безработице . В то же время прогнозирование волатильности является относительно простой и реализуемой задачей .
К техническим вызовам quant-инженеры относят переобучение (overfitting), когда модель отлично работает на исторических данных, но проваливается на тестовых , проблему обобщающей способности (generalization) , нестационарность данных и смену рыночных режимов . Наконец, сложные модели (например, нейросети) часто представляют собой «черный ящик», что затрудняет их интерпретацию . Стандартный рабочий процесс включает в себя сбор данных, формулирование гипотезы, написание кода, обучение модели и обязательное историческое тестирование .
Ошибка выжившего в бэктестинге и практическая подготовка данных 11:16
При проведении бэктестинга критически важно избегать методологических ошибок, главной из которых является ошибка выжившего (survivorship bias) [11:16, 11:29]. Она возникает, когда тестирование проводится на актуальном (сегодняшнем) списке компонентов индекса, например, S&P 500 .
Суть этой проблемы заключается в следующем: компании, которые обанкротились, показали худшие результаты или были поглощены, исключаются из индекса . Например, если в конце прошлого года некоторая компания демонстрировала затяжное падение и в декабре была исключена из S&P 500 , то при оптимизации портфеля в ноябре реальный трейдер мог бы купить ее акции и понести убытки . Однако, используя современный список компонентов индекса, мы просто не увидим эту компанию в исторических данных, что искусственно завысит историческую доходность стратегии на бэктесте [12:08, 12:22]. В рамках данного учебного проекта автор использует актуальный список S&P 500 и обращает внимание на то, что это допущение является сознательным ограничением бэктеста .
Практическая часть проекта начинается с загрузки списка акций S&P 500 с помощью парсинга таблиц Wikipedia через функцию pandas.read_html . Чтобы избежать сбоев при последующей загрузке данных из Yahoo Finance, тикеры проходят предварительную очистку: точки в названиях заменяются на дефисы . Для симуляции берется временной интервал в 8 лет, отсчитываемый назад от 27 сентября 2023 года [18:46, 18:59].
Загруженный датафрейм преобразуется методом .stack(), переводящим данные в удобный формат мультииндекса с уровнями «Дата» и «Тикер» . Это позволяет эффективно структурировать почти миллион строк информации для 500 компаний . В завершение этапа подготовки названия колонок приводятся к нижнему регистру , подготавливая базу для расчета первых количественных характеристик.
Далее в курсе авторы переходят к расчету и масштабированию технических индикаторов, отбору наиболее ликвидных акций и расчету факторных бет Фама-Френча [22:04, 23:44], однако эти темы подробно разбираются в следующей главе.
🛠️ Масштабирование признаков, фильтрация ликвидности и интеграция факторов Фама-Френча 25:04
Расчет и нормализация технических индикаторов 25:18
Подготовка сырых рыночных данных к использованию в моделях машинного обучения требует тщательного расчета технических индикаторов и их последующего масштабирования. Первым шагом является расчет линий Боллинджера (Bollinger Bands). Для этого используется библиотека pandas_ta . Расчет производится на основе логарифма скорректированной цены закрытия (np.log1p) с окном в 20 дней [25:59, 26:14]. Поскольку функция ta.bbands возвращает сразу пять колонок, разработчику необходимо извлечь три ключевые линии: нижнюю (Lower Band), среднюю (Middle Band) и верхнюю (Upper Band) [25:32, 25:46]. Это реализуется через группировку по тикерам и применение метода transform с индексацией соответствующих колонок через .iloc [25:59, 26:41].
# Пример расчета полос Боллинджера с логарифмированием
df['BBL'] = df.groupby(level=1)['adj close'].transform(lambda x: ta.bbands(np.log1p(x), length=20).iloc[:, 0])
Для расчета среднего истинного диапазона (ATR) требуется более сложный подход. Функция ATR из библиотеки pandas_ta требует на вход три колонки: максимальную (High), минимальную (Low) и цену закрытия (Close) [27:25, 28:03]. Метод transform в pandas оптимизирован для работы с одиночными сериями, поэтому для многоколоночного ввода создается пользовательская функция compute_atr [27:37, 28:28]. Внутри нее применяется группировка по тикерам с параметром group_keys=False, чтобы избежать нежелательного дублирования временных индексов при объединении данных [29:10, 29:23].
Важнейшим этапом является нормализация признаков. ATR и схождение-расхождение скользящих средних (MACD, рассчитываемый по цене закрытия с окном 20) подвергаются стандартному масштабированию: из каждого значения вычитается среднее, после чего результат делится на стандартное отклонение [28:55, 30:02, 30:29]. Такая нормализация критически важна, поскольку в дальнейшем эти признаки будут переданы в модель машинного обучения (в частности, для кластеризации, о чем подробнее пойдет речь в следующей главе) [30:29, 31:25]. Единственным индикатором, который намеренно не подвергается нормализации на этом этапе, остается индекс относительной силы (RSI) .
В завершение первого этапа рассчитывается показатель долларового объема торгов (Dollar Volume), определяемый как произведение скорректированной цены закрытия на объем торгов . Для удобства масштабирования и интерпретации полученное значение делится на 1 000 000 .
Агрегация данных и отбор 150 наиболее ликвидных акций 32:46
Для сокращения времени обучения будущих моделей машинного обучения и ускорения экспериментов с торговыми стратегиями применяется фильтрация акций по ликвидности . Процесс начинается с агрегации ежедневных данных до месячного уровня . При этом для технических индикаторов и цен закрытия берется последнее значение месяца, а для долларового объема вычисляется среднее арифметическое за весь месяц .
С технической точки зрения этот процесс реализуется через декомпозицию структуры данных:
- Исходный DataFrame «разворачивается» по тикерам с помощью метода
unstack. - Для расчета среднего долларового объема применяется ресемплирование по месячному индексу
resample('M').mean(), после чего данные «сворачиваются» обратно черезstack[33:38, 33:52]. - Для остальных признаков и цен закрытия применяется аналогичная схема, но с методом
.last()[35:56, 36:11]. - Результаты объединяются с помощью
pd.concatпо осиaxis=1.
Чтобы отсеять неликвидные активы и избежать рыночного шума, рассчитывается 5-летнее скользящее среднее месячного долларового объема для каждой акции . Размер скользящего окна составляет 60 месяцев (5 лет * 12 месяцев) [37:39, 37:51]. На основе этого показателя каждый месяц проводится кросс-секционное ранжирование акций от наибольшего объема к наименьшему с помощью метода groupby(level=0).rank(ascending=False) [38:17, 38:30]. В итоговый набор данных проходят только те акции, чей ранг строго меньше или равен 150 [38:59, 39:14].
Дополнительно для захвата ценового импульса (моментум-эффекта) рассчитываются исторические доходности акций за различные временные горизонты: 1, 2, 3, 6, 9 и 12 месяцев [39:55, 40:34]. Чтобы минимизировать влияние аномальных рыночных выбросов на будущую модель, доходности подвергаются винзорированию (клиппингу) на уровнях 0.5% и 99.5% квантилей [41:15, 41:29]. Расчет выполняется с помощью кастомной функции calculate_returns и группировки по тикерам .
Интеграция и расчет факторных бет Фама-Френча 44:43
Чтобы оценить чувствительность выбранных акций к общим систематическим факторам риска, в модель интегрируется классическая пятифакторная модель Фама-Френча [44:43, 45:13]. Эти пять факторов — рыночный риск (Market Risk), размер (Size/SMB), стоимость (Value/HML), рентабельность (Profitability/RMW) и инвестиционная активность (Investment/CMA) — исторически объясняют значительную часть доходности акций и широко применяются в индустрии управления активами [45:25, 45:38].
Данные факторов загружаются напрямую из библиотеки Кеннета Френча (Ken French Data Library) с помощью пакета pandas_datareader (импортированного как web) [45:52, 46:05]. Используется ежемесячный набор данных 'F-F_Research_Data_5_Factors_2x3' начиная с 2010 года .
# Загрузка 5 факторов Фама-Френча через pandas_datareader
ff_data = web.DataReader('F-F_Research_Data_5_Factors_2x3', 'famafrench', start='2010')[0]
Перед проведением расчетов данные факторов проходят предобработку:
- Колонка безрисковой ставки (
RF) удаляется, так как она не используется в рамках текущего исследования . - Индекс преобразуется в формат даты и сдвигается к концу месяца для точного совмещения со скользящими датами основного датафрейма акций [47:41, 48:21].
- Все значения факторов делятся на 100, так как в исходном источнике они представлены в процентах .
Подготовленные факторы объединяются с одномесячной доходностью акций . Это позволяет сопоставить доходность акции на конец месяца с факторами, известными на начало этого же месяца .
Факторные беты (чувствительности) рассчитываются путем оценки параметров линейной регрессии методом наименьших квадратов (OLS) . Для этого применяется скользящее окно регрессии (Rolling OLS) из библиотеки statsmodels . Данный подход позволяет получить динамические во времени коэффициенты чувствительности для каждой из 150 наиболее ликвидных акций, что существенно обогащает итоговый набор признаков перед этапом кластеризации [49:15, 50:08].
CHAPTER-META--- {"summary": "В главе подробно описан процесс технического анализа и предобработки данных: вычисление и нормализация индикаторов (Bollinger Bands, ATR, MACD), фильтрация 150 самых ликвидных акций на основе 5-летнего долларового объема, а также подготовка к расчету факторных бет Фама-Френча через интеграцию внешних данных и регрессионный анализ.", "quotes": [{"text": "...we would like to get the average dollar volume for the whole month for each stock... we do that to reduce training time for any potential machine learning model.", "speaker": "Instructor", "time": "32:59"}, {"text": "We are normalizing the data because we are going to use it into a machine learning model. We are going to cluster the data we want to do that straight away.", "speaker": "Instructor", "time": "30:29"}], "key_facts": ["Нормализация признаков (ATR, MACD) выполняется делением разности с математическим ожиданием на стандартное отклонение.", "RSI намеренно остается ненормализованным для последующего этапа кластеризации.", "Фильтрация топ-150 акций проводится по 5-летнему скользящему среднему долларовому объему с ежемесячным пересчетом рангов.", "Факторы Фама-Френча скачиваются через pandas_datareader, делятся на 100 и выравниваются по датам конца месяца для регрессионного анализа."], "covered_topics": ["Расчет и масштабирование технических индикаторов", "Отбор наиболее ликвидных акций", "Расчет факторных бет Фама-Френча"]}
📈 Борьба с look-ahead bias и кластеризация активов по RSI 50:22
Устранение эффекта заглядывания в будущее при расчете факторных бет 58:34
Прежде чем приступать к интеграции машинного обучения в торговую систему, необходимо убедиться в корректности и чистоте подготовленных данных. На этапе предварительной обработки автор выявляет критическую ошибку в индексации дат, из-за которой в датасете осталось всего 23 месяца наблюдений вместо ожидаемых 71 . После исправления фильтрации по долларовому объему (ранее в разговоре обсуждался отбор наиболее ликвидных акций) история успешно восстанавливается до полного периода с октября 2017 года .
Для стабильной работы регрессионных моделей крайне важно исключить из выборки активы с недостаточным объемом исторических данных. Автор вводит фильтр, отсекающий любые акции, имеющие менее 10 месяцев наблюдений . Это предотвращает сбои при расчете скользящих окон . Расчет факторных бет (ранее в разговоре они рассчитывали факторные беты Фама-Френча) производится с помощью скользящей OLS-регрессии с окном в 24 месяца . Если у акции накоплено меньше 24 месяцев истории, но больше установленного лимита в 10 месяцев, размер окна динамически сужается до доступного количества строк .
Главная методологическая опасность на этом этапе — эффект заглядывания в будущее (look-ahead bias) . Поскольку для расчета текущей беты используются значения факторов на начало месяца и доходность на конец месяца, итоговое значение коэффициента становится известным инвестору только в самом конце расчетного периода . Прямое объединение этих бет с признаками текущего месяца приведет к неявной передаче информации из будущего в обучающую выборку .
Для устранения этой уязвимости автор применяет сдвиг данных на один шаг вперед . Однако стандартный метод сдвига DataFrame .shift(1) использовать нельзя: он сдвинет строки глобально, из-за чего данные одного тикера (например, Verizon) перетекут в строки другого . Решением становится группировка по тикерам перед сдвигом:
data.groupby(level='ticker').shift(1) .
После сдвига в данных неизбежно возникают пропуски (NaN). Их заполнение (импутация) выполняется путем группировки по каждому отдельному тикеру и вычисления среднего значения для каждого конкретного фактора . Наконец, из итогового набора данных удаляется колонка скорректированной цены закрытия (adjusted close), оставляя чистую матрицу из 18 признаков, готовую для передачи в модели машинного обучения .
Кластеризация K-Means для формирования портфеля 1:04:09
Сформировав качественный массив признаков, команда переходит к этапу генерации торговых сигналов . Вместо построения сложных прогнозных моделей с учителем (supervised learning) для предсказания точной доходности или весов активов, автор предлагает использовать методы обучения без учителя (unsupervised learning) . Задача состоит в том, чтобы ежемесячно группировать 150 самых ликвидных акций на рынке в однородные кластеры по их характеристикам .
Для решения этой задачи применяется алгоритм кластеризации K-Means . На основе предварительных исследований автор устанавливает оптимальное количество кластеров, равное 4 . Этот параметр позволяет сбалансированно разделять активы без избыточного дробления выборки .
Процесс кластеризации реализуется через создание функции get_clusters . Внутри нее инициализируется объект класса KMeans из библиотеки scikit-learn со следующими параметрами:
-
random_state=0— фиксация генератора случайных чисел для воспроизводимости результатов между запусками ; -
init='random'— базовая случайная инициализация центроидов на первом этапе тестирования модели .
Алгоритм применяется ежемесячно с помощью конструкции группировки по датам:
data.groupby(level='date').apply(get_clusters) .
В результате работы модели каждая акция в датасете получает метку кластера от 0 до 3 для каждого конкретного месяца .
Проблема случайной инициализации и кастомные центроиды по RSI 1:09:05
Для оценки качества работы алгоритма автор visualizes полученные группы . Поскольку работать одновременно с 18 признаками на плоскости невозможно, для наглядности строятся двумерные диаграммы рассеяния (scatter plots), где по оси абсцисс откладывается индикатор ATR, а по оси ординат — ненормализованные значения индекса относительной силы (RSI) .
Визуализация подтверждает высокую эффективность группировки: во все месяцы акции четко разделяются на горизонтальные зоны в зависимости от уровня их перекупленности или перепроданности . В частности, стабильно выделяются:
Однако при детальном анализе обнаруживается серьезная проблема: из-за случайной инициализации центроидов метки кластеров хаотично меняются местами от месяца к месяцу . Если в октябре кластер с меткой 0 соответствовал акциям с RSI 35, то в ноябре под меткой 0 может оказаться группа с RSI 50 .
Для импульсной (momentum) торговой стратегии, цель которой — регулярно покупать исключительно активы с сильным восходящим трендом (RSI от 65 до 75), такая нестабильность фатальна . Торговому роботу необходимо точно знать, какой именно номер кластера соответствует зоне максимального импульса в текущем месяце .
Чтобы решить эту проблему, автор предлагает отказаться от стандартной случайной инициализации init='random' и передавать в K-Means предопределенные начальные координаты центроидов через параметр init . В качестве опорных точек для инициализации выбираются целевые значения RSI: 30, 45, 55 и 70 . Программа создает массив нулевых векторов размерностью (4, 18) (по числу кластеров и признаков) и заполняет в них только координаты, соответствующие индексу признака RSI. Это вынуждает алгоритм K-Means всегда привязывать метку кластера к строго определенному диапазону RSI, обеспечивая стабильную сортировку активов для дальнейшего отбора в портфель.
📈 Оптимизация портфеля по максимуму Шарпа и динамическое ребалансирование 1:18:32
Отбор акций на основе momentum-эффекта и подготовка временной структуры 1:18:32
Ранее в рамках разработки торговой системы авторы реализовали алгоритм кластеризации K-Means для группировки активов по техническим индикаторам . Теперь, когда кластеры стабильно идентифицируются из месяца в месяц и за каждым из них закрепляется определенное рыночное состояние , наступает этап практического применения полученных данных. Согласно принятой momentum-гипотезе, акции с высоким показателем RSI (находящимся в районе 70, что стабильно соответствует кластеру под номером 3) демонстрируют сильный ценовой импульс. Предполагается, что этот импульс продолжит работать и позволит бумагам опережать рынок в течение следующего месяца .
Для реализации этой стратегии на стыке месяцев (например, 31 октября) отбирается список подходящих тикеров . Процесс подготовки данных в Pandas включает сброс первого уровня мультииндекса и смещение дат на один день вперед с использованием DateOffset . Это необходимо для того, чтобы перенести точку принятия решений на точную дату начала инвестиционного периода — первое число следующего месяца .
Итогом этого этапа становится формирование словаря fixed_dates . Ключами в данном словаре выступают строковые даты начала торговых периодов, а значениями — списки тикеров, приведенные к стандартному формату Python-list . Этот словарь служит мостом между модулем машинного обучения и блоком портфельной оптимизации .
Математическое ядро стратегии: PyPortfolioOpt и диверсификационные ограничения 1:23:48
Переходя от простого отбора акций к математическому распределению весов капитала, авторы обращаются к современной портфельной теории (MPT) Гарри Марковица . Для этого используется специализированная библиотека PyPortfolioOpt, из которой импортируются класс EfficientFrontier, а также модули risk_models и expected_returns .
Основная задача оптимизатора — максимизация коэффициента Шарпа, то есть поиск такого соотношения активов, которое обеспечивает наибольшую избыточную доходность на единицу риска. Написанная авторами функция optimize_weights принимает на вход массив исторических цен . Внутри функции рассчитывается среднегодовая историческая доходность через метод mean_historical_return , а также ковариационная матрица с помощью sample_cov . В обоих случаях задается рабочая частота в 252 торговых дня в году .
Особое внимание авторы уделяют ограничениям весов (weight bounds) для минимизации портфельных рисков. Стандартные настройки класса EfficientFrontier допускают распределение долей от 0 до 100% на один актив . Чтобы избежать опасной концентрации капитала в одной-двух бумагах, вводится жесткое верхнее ограничение: доля любого отдельного актива в портфеле не может превышать 10% (0.1) .
Нижняя граница веса (lower bound) настраивается динамически . Авторы предлагают привязывать ее к числу вошедших в портфель активов в конкретном месяце. Например, если в портфель отобрано 20 акций, то при равном распределении доля каждой составит 5% . Минимальный порог в таком случае устанавливается как половина от равного веса (то есть 2.5%) .
После настройки ограничений вызывается метод оптимизации ef.max_sharpe(), а итоговые веса обрабатываются функцией clean_weights(), которая округляет незначительные значения и отсекает нулевые доли . Такой подход гарантирует получение хорошо диверсифицированного и сбалансированного портфеля .
Реализация контура ребалансировки и скользящего окна оптимизации 1:30:55
Чтобы протестировать стратегию на исторических данных, требуется загрузить ежедневные цены для всего пула из 150 наиболее ликвидных акций исходного датасета . Для этого используется библиотека yfinance . Поскольку для первой оптимизации на дату 1 ноября 2017 года необходим как минимум один год предшествующей ценовой истории, стартовая дата загрузки динамически сдвигается на 12 месяцев назад с помощью вычитания временного смещения .
После успешного скачивания цен рассчитываются ежедневные логарифмические доходности активов . Далее запускается основной цикл ребалансировки по ключам словаря fixed_dates . На каждой итерации (в начале очередного месяца) определяются границы текущего инвестиционного окна: дата входа и дата выхода, рассчитываемая через смещение конца месяца month end .
Для корректного расчёта весов на текущий месяц из общего массива данных извлекается строго определенное скользящее окно исторических цен . Данное окно начинается ровно за 12 месяцев до даты ребалансировки и заканчивается за один день до нее . Срез цен по выбранным для портфеля тикерам передается в оптимизатор, который на основе данных прошедшего года генерирует оптимальные веса для удержания активов в течение следующего месяца.
5. Отказоустойчивость бэктеста и переход к сентимент-анализу соцсетей 1:40:20
Резервные стратегии при сбое оптимизатора: Реализация отказоустойчивости бэктеста 1:52:06
В количественном трейдинге переход от теоретической оптимизации портфеля к автоматизированному бэктесту на исторических данных часто выявляет краевые сценарии, способные полностью остановить работу скрипта. В процессе ежемесячного пересчета весов алгоритм определяет окно оптимизации — например, с 1 ноября 2016 года по 30 октября 2017 года . Далее накладываются ограничения: максимальный вес одного актива не должен превышать 10% , а динамический нижний предел устанавливается как половина от веса в гипотетическом портфеле с равными долями . При наличии 10 акций в выбранном месяце равное распределение дало бы по 10% на каждую бумагу; соответственно, минимальный лимит на акцию фиксируется на уровне 5% (или 0.05) , который затем округляется для чистоты расчетов .
Однако при итеративном запуске этой процедуры на протяжении нескольких лет математический оптимизатор (такой как CVXPY) периодически сталкивается с вычислительными трудностями. Из-за экстремальных рыночных фаз или аномально высокой корреляции активов алгоритм не может найти корректное решение, удовлетворяющее всем жестким ограничениям при максимизации коэффициента Шарпа. В этот момент оптимизатор выдает ошибку смены статуса на Infeasible (недопустимое решение) . В стандартном бэктестере это привело бы к тому, что портфель остался бы без весов , а инвестор полностью пропустил бы этот месяц на рынке, зафиксировав нулевую доходность .
Для создания отказоустойчивой системы промышленного уровня в код внедряется блок обработки исключений try-except . Основная резервная стратегия проста: если оптимизация максимального коэффициента Шарпа дает сбой, портфель автоматически распределяется в равных долях . Логика отслеживается с помощью булевой переменной success, которая изначально имеет значение False . Если оптимизатор успешно находит веса, флаг меняется на True . В случае сбоя срабатывает блок except, на экран выводится предупреждение "Max Sharpe optimization failed... continuing with equal weights" , а бэктестер генерирует Pandas DataFrame с равным распределением долей (1, деленное на число доступных активов) для всех бумаг текущего месяца [1:55:58 - 1:56:41]. Такой подход гарантирует непрерывность тестирования на всем историческом интервале .
Оценка результатов: Сравнение стратегии с бенчмарком S&P 500 1:58:20
Когда доходность портфеля рассчитана без пропусков по всей временной шкале, наступает этап оценки эффективности. Для этого система загружает исторические данные по ETF SPY, который отслеживает индекс S&P 500 . Загрузка охватывает период с 1 января 2015 года по текущую дату с помощью API библиотеки Yahoo Finance [1:58:46 - 1:59:03].
Для корректного сопоставления результатов цены закрытия SPY преобразуются в логарифмическую доходность с применением функций .diff() и .dropna() . Полученный столбец переименовывается в "SPY Buy and Hold", становясь эталоном для сравнения . Данные бенчмарка объединяются с результатами нашей стратегии методом merge по датам .
Накопленная доходность как для торговой стратегии, так и для индекса S&P 500 рассчитывается через расширяющееся окно экспоненциальной суммы логарифмических доходностей . Результаты визуализируются в стиле ggplot библиотеки Matplotlib с размером графика 16x6 . Для придания графику профессионального вида шкала оси Y форматируется в процентах . Для этого импортируется модуль matplotlib.ticker под псевдонимом MTI , после чего к оси применяется соответствующий метод форматирования . Полученная диаграмма наглядно демонстрирует, как портфель, сформированный на базе машинного обучения, показывает себя на фоне стандартного пассивного инвестирования в индекс .
Анализ альтернативных данных и сентимента акций NASDAQ 100 2:05:05
Завершив построение портфеля на основе машинного обучения, курс переходит к изучению другого направления современного количественного анализа — торговле на основе альтернативных данных . Вместо того чтобы ограничиваться стандартными ценовыми графиками и финансовыми отчетами компаний, во втором проекте создается торговая стратегия на базе анализа настроений (сентимента) в Твиттере .
Входящий конвейер данных считывает специализированный датасет, содержащий информацию о сентименте пользователей Твиттера по отношению к акциям, входящим в технологический индекс NASDAQ 100 [2:05:19 - 2:05:32]. Несмотря на то что социальные сети выступают мощным источником рыночных сигналов (альфы), они содержат колоссальный объем информационного мусора. Розничные инвесторы, спам-аккаунты и специализированные боты регулярно пытаются манипулировать объемами упоминаний для искусственного разгона котировок отдельных тикеров.
Для борьбы с этим явлением вводится метрика Engagement Ratio (коэффициент вовлеченности). Вместо простого подсчета общего количества твитов с упоминанием акций или усреднения базовых показателей сентимента, Engagement Ratio взвешивает социальную активность с учетом реального отклика аудитории: лайков, ретвитов и комментариев.
Внезапный всплеск упоминаний от свежесозданных профилей с нулевым числом лайков или ретвитов классифицируется алгоритмом как бот-активность и отсекается. Напротив, публикации от авторитетных или вовлеченных участников обсуждения, вызывающие живую дискуссию, получают высокий приоритет. Подобная фильтрация позволяет отсеивать шум и использовать только реальные, весомые сигналы для принятия инвестиционных решений и последующего формирования портфеля .
📊 Бэктестинг портфельной стратегии на основе сентимента из Twitter 2:05:45
Очистка данных и вычисление коэффициента вовлеченности (Engagement Ratio) 2:05:45
Для реализации стратегии, основанной на альтернативных данных, сначала необходимо подготовить рабочую среду и импортировать ключевые библиотеки: pandas, numpy, matplotlib, datetime, yfinance и системный модуль os . В качестве стилизации графиков традиционно выбирается стиль ggplot . Исходные данные загружаются из CSV-файла sentiment_data.csv . Этот датасет содержит информацию о дате, тикере (символе) акции, количестве постов в Twitter, комментариях, лайках, показах (impressions) и итоговом показателе сентимента, рассчитанном провайдером данных .
Первым шагом предобработки является конвертация колонки дат в формат datetime и создание мультииндекса, состоящего из даты и тикера . Главная проблема сырых данных из соцсетей заключается в обилии ботов, которые сильно искажают реальную картину популярности активов . Чтобы отфильтровать этот шум, вместо использования абсолютного сентимента или количества просмотров вводится производная метрика — коэффициент вовлеченности (Engagement Ratio) . Он рассчитывается как отношение количества комментариев к количеству лайков под постами об акции :
$$\text{Engagement Ratio} = \frac{\text{Twitter Comments}}{\text{Twitter Likes}}$$
Если у компании много лайков, но совсем нет комментариев, это с высокой долей вероятности указывает на накрутку ботами . Высокий коэффициент вовлеченности, напротив, свидетельствует о живом интересе со стороны реальных инвесторов . Для исключения случайных выбросов применяется жесткий фильтр: в инвестиционную вселенную попадают только те акции, которые за день набрали более 20 лайков и более 10 комментариев .
Агрегация, кросс-секционное ранжирование и выбор топ-акций 2:10:57
После первичной фильтрации необходимо рассчитать среднемесячный коэффициент вовлеченности для каждой акции . Для этого индекс по тикерам временно сбрасывается, и данные группируются по двум уровням — месяцу и символу акции . На выходе формируется агрегированный датафрейм со средними значениями Engagement Ratio за каждый месяц .
Следующий ключевой шаг — кросс-секционное ранжирование активов . Процедура выполняется ежемесячно с помощью группировки по дате и применения функции ранжирования в порядке убывания (ascending=False) . Акции с наибольшим уровнем вовлеченности получают наивысший ранг .
Из полученного списка для каждого месяца отбираются топ-5 акций с рангом меньше или равным 5 . Для формирования портфеля на следующий месяц к индексу даты добавляется один день с помощью pandas.DateOffset . Это позволяет получить точную дату начала инвестиционного периода (первый день следующего месяца) и список активов для покупки .
Из уникального списка всех отобранных акций загружаются исторические котировки через API Yahoo Finance за период с 1 января 2021 года по 1 марта 2023 года .
Симуляция равновзвешенного портфеля и сравнение с бенчмарком QQQ 2:18:24
На основе скорректированных цен закрытия рассчитываются ежедневные логарифмические доходности для каждой акции . Моделирование портфеля происходит в цикле по ключевым датам . Для каждого месяца определяется дата его окончания с использованием смещения month end . Внутри каждого временного интервала выбираются топ-5 акций, актуальных для этого месяца, и вычисляется их средняя ежедневная доходность . Таким образом формируется равновзвешенный портфель с ежемесячным ребалансированием .
Здесь важно сделать оговорку, о которой авторы упоминали ранее в курсе: используемый набор акций не очищен от ошибки выжившего (survivorship bias) , что может несколько завышать итоговые результаты бэктеста.
В качестве бенчмарка для сравнения выбирается ETF на индекс NASDAQ — QQQ . Для него загружаются аналогичные исторические цены и рассчитывается ежедневная логарифмическая доходность . Объединив доходности портфеля и QQQ в один датафрейм , рассчитывается их накопленная (кумулятивная) доходность с использованием расширяющегося окна (expanding window) и суммы накопленных доходностей .
Результаты визуализируются на графике с процентным форматом оси Y . Бэктест показывает следующие результаты:
- При ранжировании акций исключительно по количеству лайков стратегия значительно уступает бенчмарку QQQ .
- Ранжирование только по комментариям дает показатели чуть выше бенчмарка .
- Использование комплексного коэффициента вовлеченности (Engagement Ratio) позволяет стабильно обходить индекс NASDAQ на исследуемом промежутке времени .
Это наглядно доказывает, что правильная обработка альтернативных данных и создание производных признаков (derivative features) способны генерировать реальную альфу для торговых стратегий . В дальнейшем авторы переходят к разбору интрадей-стратегий с использованием моделей прогнозирования волатильности .
7. Прогнозирование волатильности с GARCH и мультивременные торговые сигналы 2:31:03
Настройка и импорт данных для интрадей-стратегии 2:31:03
Для построения продвинутой торговой стратегии на стыке дневных и внутридневных интервалов необходимо правильно подготовить и синхронизировать исторические данные. Процесс начинается с загрузки дневных котировок, на которых рассчитывается логарифмическая доходность с использованием метода .diff() . Параллельно в систему загружается датасет с симулированными 5-минутными барами через стандартный метод pd.read_csv библиотеки Pandas .
В полученном пятиминутном датафрейме критически важно привести индекс к временному формату. Для этого колонка времени преобразуется с помощью функции pd.to_datetime и назначается в качестве индекса таблицы . На основе обновленного временного индекса генерируется дополнительное поле date без указания внутридневного времени . Эта колонка в дальнейшем послужит ключевым связующим звеном для объединения (merge) дневных сигналов с интрадей-сеткой .
Моделирование волатильности с помощью GARCH(1,3) 2:32:49
Центральным математическим элементом прогнозирования дневной волатильности актива выступает модель GARCH — обобщенная авторегрессионная условная гетероскедастичность . Прогнозирование дисперсии на один день вперед осуществляется в скользящем окне глубиной 6 месяцев (или 180 торговых дней) .
Для спецификации модели GARCH исследователю необходимо определить порядок авторегрессии (обозначаемый как $p$) и порядок скользящего среднего ($q$) . В рамках рассматриваемого курса автор применил метод полного перебора (Brute Force), обучив 16 различных конфигураций моделей, где параметры $p$ и $q$ варьировались в диапазоне от 1 до 4 . Оценка моделей производилась по двум ключевым показателям: среднеквадратичной ошибке (MSE) и Байесовскому информационному критерию (BIC) . В результате лучшей моделью, минимизирующей критерий BIC на используемых данных, была признана архитектура GARCH(1,3), где $p = 1$, а $q = 3$ .
Непосредственно перед запуском прогнозов выполняются следующие шаги:
- На исторических дневных данных рассчитывается скользящая полугодовая дисперсия логарифмической доходности .
- Датасет фильтруется для ускорения расчетов, оставляя исторический интервал с начала 2020 года .
- Описывается функция прогнозирования
predict_volatility, которая инициализирует объектarch_modelс фиксированными параметрами авторегрессии $p=1$ и $q=3$ , .
Внутри функции модель обучается с параметром частоты обновления логов update_freq=5 при отключенном выводе технической информации на экран . Прогноз строится ровно на один шаг вперед (Horizon=1) , после чего из результатов работы модели извлекается прогнозное значение дисперсии для последней доступной даты . Запуск этой функции в скользящем окне через конструкцию apply и лямбда-выражение производит пошаговый расчет для всего тестового набора данных .
Расчет премии за волатильность и генерация дневного сигнала 2:38:31
Полученные точечные прогнозы дисперсии используются для вычисления премии за прогнозируемую волатильность (Prediction Premium) . Формула этой метрики выглядит следующим образом:
$$\text{Prediction Premium} = \frac{\text{Predictions} - \text{Variance}}{\text{Variance}}$$
Из прогнозного значения дисперсии вычитается фактическая скользящая историческая дисперсия, а полученная разность нормируется (делится) на ту же фактическую историческую дисперсию . Далее рассчитывается скользящее стандартное отклонение полученной премии за 6 месяцев (180 дней) .
На основе этих расчетов формируется ежедневный бинарный сигнал:
- Значение
1(сигнал к покупке/лонгу на день) генерируется, если значение премии за волатильность превышает свое скользящее стандартное отклонение более чем в 1.5 раза . - Значение
-1(сигнал к продаже/шорту) генерируется, если премия падает ниже уровня минус 1.5 стандартных отклонений . - Значение
NaNприсваивается во всех прочих случаях, когда премия колеблется в рамках стандартного диапазона .
Для оценки распределения сигналов строится гистограмма . Она наглядно демонстрирует, что за исследуемый период стратегия генерирует около 50 дневных сигналов на покупку и порядка 35 сигналов на продажу . Чтобы избежать классической ошибки заглядывания в будущее (которая подробно разбиралась ранее в курсе), полученный дневной сигнал сдвигается ровно на один торговый день вперед методом .shift(1) . Это обусловлено тем, что дневной сигнал, сформированный на закрытии дня $T$, может быть практически применен только на следующий торговый день $T+1$ . После сдвига дневной датафрейм объединяется с 5-минутными внутридневными данными с помощью функции merge() по дате .
Интрадей-фильтры и совмещение сигналов для входа в позицию 2:45:27
После завершения слияния таблиц и очистки итогового датафрейма от дублирующих временных колонок импортируется специализированная библиотека технического анализа pandas_ta . Для интрадей-данных рассчитываются два классических индикатора: 20-периодный индекс относительной силы (RSI) и линии Боллинджера (Bollinger Bands) . Из многоколоночного вывода линий Боллинджера отбираются крайние границы: нижняя линия (первый столбец данных) и верхняя линия (третий столбец) .
Внутридневной сигнал призван фиксировать сильные ценовые импульсы:
- Покупка (
1) фиксируется, если индикатор RSI поднимается выше уровня 70, а цена закрытия 5-минутного бара пробивает верхнюю границу полосы Боллинджера . - Продажа (
-1) фиксируется при падении RSI ниже 30 и закрытии бара под нижней полосой Боллинджера .
Финальное торговое решение принимается на пересечении дневного прогноза GARCH и интрадей-импульса. Логика входа строится на гипотезе о возврате к среднему (mean reversion): если дневной сигнал указывает на аномально высокую прогнозную волатильность (signal_daily == 1), а интрадей-рынок при этом перекуплен (signal_intraday == 1), то открывается короткая позиция (short) . Напротив, если оба сигнала принимают значение -1 (дневная волатильность низкая, а интрадей-рынок экстремально перепродан), инициируется длинная позиция (long) .
Правило исполнения предполагает вход в сделку при первом появлении совмещенного сигнала в течение дня с последующим удержанием позиции до конца торговой сессии . Любые повторные сигналы внутри того же дня игнорируются . В программном коде это реализуется группировкой данных по дням через pd.Grouper и применением метода transform с лямбда-функцией forward fill (ffill) . Этот прием распределяет («протягивает») знак первой открытой позиции на все последующие 5-минутные бары до момента автоматического закрытия в конце дня .
📈 Расчет доходности и визуализация результатов интрадей-стратегии 2:55:47
Векторизованный расчет пятиминутных доходностей без избыточных группировок 2:56:02
При оценке эффективности торговых стратегий на исторических данных критически важно правильно выстроить процесс векторизованных вычислений, чтобы избежать ресурсоемких циклов. На этапе подготовки пятиминутных данных нет необходимости применять ресурсоемкую группировку groupby по дням для каждого шага вычислений . Поскольку анализируемый датасет представляет собой непрерывный поток пятиминутных интервалов от начала и до конца каждого дня, включая выходные, мы можем проводить расчеты напрямую по всему массиву .
Достаточно рассчитать пятиминутную доходность и сдвинуть полученный вектор на одну строку назад с помощью метода shift(-1) . Этот сдвиг необходим для того, чтобы сопоставить сигнал, сгенерированный в текущий момент времени, с доходностью, которая будет получена на следующем шаге. Векторизованный расчет доходности самой стратегии реализуется путем простого перемножения будущей доходности (forward_return) на знак сгенерированного торгового сигнала . В результате такой операции мы получаем вектор доходности на пятиминутном уровне . Использование векторизации в библиотеке Pandas позволяет мгновенно обрабатывать даже многолетние массивы интрадей-данных без зависания интерпретатора Python.
Агрегация результатов на дневной уровень и расчет кумулятивной доходности 2:56:41
Получив вектор доходности на уровне пятиминутных интервалов, необходимо перейти к более репрезентативному временному масштабу для оценки общей динамики торговой системы . Переход осуществляется с помощью метода группировки groupby по датам, где для каждого отдельного дня выбирается колонка доходности стратегии, а затем к ней применяется функция суммирования . Таким образом, все интрадей-результаты внутри дня складываются, формируя единый временной ряд дневных доходностей .
После успешного получения дневного датафрейма можно переходить к финальному шагу бэктеста — расчету кумулятивной доходности всей стратегии . Для корректного отражения сложного процента при использовании логарифмических доходностей применяется следующая математическая последовательность:
-
Расчет кумулятивной суммы дневных доходностей с помощью метода
cumsum(). -
Применение экспоненциальной функции NumPy (
np.exp) к полученному вектору кумулятивных логарифмических доходностей для перевода их в линейный масштаб . -
Вычитание единицы из конечного результата для корректного отображения чистого прироста капитала в процентном отношении .
Этот подход позволяет точно смоделировать рост или падение условного торгового депозита на протяжении всего исторического периода тестирования , минимизируя вычислительные погрешности и округления.
Визуализация эквити и анализ периодов бездействия 2:57:52
Финальным штрихом оценки интрадей-стратегии является построение графика изменения капитала (эквити). Для визуализации кумулятивной доходности используется библиотека Matplotlib, где графику присваивается понятный заголовок — "Intraday Strategy Returns" . Чтобы сделать график профессиональным и читаемым, шкала оси Y форматируется в процентах с помощью специального форматтера . Подпись оси Y задается как "Return" для однозначной интерпретации результатов .
Анализируя получившуюся кривую доходности, можно заметить характерную особенность интрадей-стратегии: она может находиться в режиме ожидания в течение длительного времени. Например, на графике отчетливо виден плоский участок продолжительностью почти шесть месяцев, когда стратегия не совершала никаких сделок . Такое поведение абсолютно нормально, так как вход в позицию происходит исключительно тогда, когда совпадают дневной и интрадей-сигналы .
Ранее в разговоре авторы касались прогнозирования волатильности с моделью GARCH и симулированных интрадей-данных, комбинация которых как раз и формирует данные условия для входа . Когда оба фильтра срабатывают одновременно, стратегия открывает позиции и генерирует прибыль . На этом разбор третьего практического проекта курса успешно завершается . Все необходимые Jupyter-ноутбуки и исходные данные доступны по ссылкам под видео, а для желающих углубиться в тему количественного трейдинга автор предлагает посетить свой специализированный веб-сайт .