Статистический арбитраж на основе коинтегрированных акций (Часть 7): Система оценки 2
Введение
В предыдущей статье мы представили предлагаемую систему оценки корзин акций. Мы остановились на результатах бэктеста, которые показали относительно хорошие показатели, но также выявили некоторые недостатки в нашей корзине.
- В среднем очень длительные сроки удержания позиций
- По крайней мере одна позиция удерживалась более шестнадцати недель! Это было максимальное время удержания позиции.
Основная цель этого бэктеста заключалась не в оценке доходности стратегии или корзины, как мы уже упоминали, а в проверке эффективности нашей системы оценки при отсутствии двух из предложенных ею критериев отбора: стабильности весов портфеля и времени возврата к среднему значению, или «времени полураспада». То есть на тот момент, когда мы закончили предыдущую статью, наша система оценки использовала только два из четырёх предложенных критериев отбора. Мы просто сформировали несколько корзин из наиболее ликвидных ценных бумаг полупроводниковой отрасли и выбрали ту из них, которая демонстрировала наибольшую коинтеграцию. Мы не оценивали, были ли веса портфеля, указанные по результатам теста Йохансена, достаточно стабильными, и не оценивали время возврата к среднему значению.
- Ликвидность
- Сила векторов коинтеграции
- Стабильность вектора коинтеграции (весов портфеля)
- Время до возврата к среднему значению (полупериод)
В этой статье мы восполним этот пробел, рассмотрев оставшиеся два аспекта: стабильность весов в портфеле и время возврата к среднему значению. Мы продолжим с этого момента и воспользуемся теми же выбранными корзинами, чтобы проверить, как система может выиграть от учета этих двух факторов ранжирования.
Для тех читателей, кто, возможно, не следит за этой серией статей: мы разрабатываем систему статистического арбитража для обычного розничного трейдера, располагающего обычным ноутбуком, ограниченными средствами и стандартной скоростью интернет-соединения. Проект начался с неформальной беседы с друзьями и превратился в вызов, который мы с партнером приняли как данность: как возможность поучиться и отточить свои навыки трейдеров. Поводом для разговора стала кончина математика и управляющего хедж-фондом Джима Симмонса, который с помощью своего легендарного фонда Medallion Fund добился рекордной серии из 30 лет непрерывной прибыльности, продемонстрировав «среднюю годовую валовую доходность в 66,1% или среднюю годовую чистую доходность в 39,1% в период с 1988 по 2018 год» благодаря использованию статистического арбитража и, по его собственным словам, «некой формы машинного обучения».
До сих пор мы рассмотрели, как применять и интерпретировать наиболее распространённые тесты на корреляцию, коинтеграцию и стационарность при торговле парами и группами портфелей (корзинами). Мы реализовали несколько скриптов на Python для анализа, два примера советников — один для торговли парами, а другой для торговли корзинами — и провели с их помощью ряд бэктестов. Кроме того, мы создали и продолжаем совершенствовать необходимую схему базы данных для обеспечения проведения наших экспериментов.
Если вас интересует метод статистического арбитража, ознакомьтесь с предыдущими статьями этой серии и попробуйте самостоятельно попрактиковаться с ними. Вы увидите, что этот разговор будет очень полезен для трейдеров, ведь мы стоим «на плечах гигантов». Профессиональные математики и статистики уже проделали за нас всю сложную работу, и мы извлекаем из этого пользу, сосредоточиваясь на торговой стороне дела — а не на сложных математических вычислениях — и широко используя готовые библиотеки с открытым исходным кодом. Поскольку мы приближаемся к завершению основной части этой серии, сейчас самое время повторить основные понятия.
Итак, давайте завершим разработку нашей системы оценки. Для начала мы изменим таблицу coint_rank, чтобы учесть отсутствующие данные — два отсутствующих фактора ранжирования.

Рис. 1. Схема, демонстрирующая обновленные поля и типы данных таблицы coint_rank
Время возврата к среднему значению
Результаты бэктеста, который мы провели для проверки нашей (наполовину реализованной) системы оценки, показали довольно странные цифры в отношении средней продолжительности удержания позиций: в среднем почти четыре с половиной недели! При этом по крайней мере одна позиция оставалась незаполненной более шестнадцати недель. Это не соответствует ожидаемому уровню риска для нашей стратегии.

Рис. 2. Скриншот отчета о бэктесте из предыдущей статьи, на котором показано время удержания позиций
Предположим, что с торговым алгоритмом и средой бэктестинга всё в порядке, и, поскольку наша стратегия основана на возврате к среднему значению, наиболее очевидным фактором, объясняющим эти странные цифры, является время, необходимое для возврата к среднему значению. Наш спред может сохраняться гораздо дольше, чем ожидалось, прежде чем вернется к среднему значению, что станет сигналом к закрытию всех хеджируемых позиций.
Помнишь, как мы выбирали корзину для торговли? Мы выбрали «наиболее» коинтегрированную корзину, то есть корзину с наибольшей степенью коинтеграции — корзину из четырёх инструментов, сформированную из десяти наиболее ликвидных инструментов полупроводниковой отрасли. Другими словами, мы оценивали по показателям ликвидности и силе коинтеграции… и больше ни по чему. Однако следующим логичным шагом является оценка того, как быстро отклонения спреда возвращаются к среднему значению. Этот показатель получил громкое название «период полураспада возврата к среднему значению». Период полураспада возврата к среднему значению определяет ожидаемое время, за которое спред (остаток от коинтеграционной зависимости) вернётся на половину пути к своему среднему значению после отклонения.
В статистическом арбитраже, где прибыль получают за счет ставок на временные расхождения в ценах, количественная оценка периода полураспада имеет решающее значение для определения сроков удержания позиций и, как следствие, для оценки жизнеспособности стратегии в целом. Позже, при рассмотрении вопросов управления капиталом и рисками, период полураспада явления возврата к среднему значению пригодится для определения объема наших позиций и размера наших сделок, поскольку более короткие периоды полураспада позволяют делать более крупные ставки из-за меньшей степени неопределенности.
Короткий период полураспада (в нашем случае от нескольких минут до нескольких часов) свидетельствует о быстром возврате к исходному состоянию и позволяет оперативно закрывать позиции. Мы снижаем уровень подверженности рискам, и наш капитал освобождается для инвестирования в новые возможности. С другой стороны, длительный период удержания (от нескольких дней до нескольких недель) приводит к замораживанию нашего капитала, увеличивает затраты на удержание позиций (такие как свопы на Форекс и комиссии за заем при коротких продажах акций) и повышает нашу подверженность рискам, связанным с новостными событиями или снижением ликвидности. Без количественной оценки периода возврата к среднему значению мы рискуем не только остаться на рынке дольше, чем ожидалось, но и выйти из него преждевременно.
Если вы хотите запомнить из этого введения только одну вещь, запомните следующее: ВОЗВРАТ К СРЕДНЕМУ НЕ ГАРАНТИРОВАН.
Позиция может оставаться открытой бесконечно. Длительные отклонения могут привести к срабатыванию стоп-лоссов или требованиям о пополнении маржи. Сделки с периодом удержания, превышающим ваш уровень толерантности к риску (например, >30 дней в условиях высокочастотной торговли), следует отфильтровывать.
Как рассчитать период полураспада
Математика, лежащая в основе расчета периода полураспада до возврата к среднему значению, может показаться сложной для тех, кто не знаком с «белой магией» математики. Период полураспада выводится на основе моделирования распределения как процесса Орнштейна–Уленбека — стохастического дифференциального уравнения, описывающего поведение с возвратом к среднему значению. В дискретном времени, что и является нашим случаем, это приближается к модели AR(1) — авторегрессионной модели первого порядка.
«Процесс Орнштейна–Уленбека используется в модели Васичека для определения процентной ставки. Процесс Орнштейна–Уленбека — один из нескольких подходов, используемых для стохастического моделирования (с некоторыми модификациями) процентных ставок, валютных курсов и цен на сырьевые товары. (...) «Одним из применений этого процесса является торговая стратегия, известная как «парная торговля»» (статья в Википедии о процессе Орнштейна-Уленбека в финансовой математике)
Я привожу вышеуказанное в качестве справочной информации для читателей, интересующихся математикой. К счастью, как уже упоминалось выше, нам не нужно вникать в математику, лежащую в основе этого расчета, поскольку для него существует формула, и мы можем положиться на нашу надежную библиотеку Python statsmodels для подбора модели AR(1). Нам нужно знать только последовательность действий для вычисления. (Ваш ИИ-помощник может оказать вам неоценимую помощь в этом деле, поскольку речь идет об очень известном вычислении, а statsmodels — о широко распространенной статистической библиотеке.)
В приложенном файле Python это реализовано в методе compute_half_life().
if len(spread) < 3: return np.nan lag = spread.shift(1).dropna() delta = (spread - lag).dropna() if len(delta) < 2: return np.nan res = sm.OLS(delta, sm.add_constant(lag, has_constant='add')).fit() # beta = res.params[1] beta = res.params.iloc[-1] if beta >= 0: return np.inf else: return -np.log(2) / beta
Перед передачей сюда необходимо проверить стационарность этого разрыва, что мы уже сделали ранее в рамках нашего алгоритма с помощью теста ADF. Данные, которые мы здесь передаем, уже являются стационарными. Кроме того, нам необходимо провести этот анализ на данных, не входящих в обучающую выборку, чтобы избежать переобучения и подтвердить прогнозную способность модели, чем мы и займёмся далее. В качестве первоначального критерия для нашей стратегии мы будем ориентироваться на периоды полураспада от четырех до двадцати часов (1–5 баров) (напомним, что мы занимаемся свинг-трейдингом на часовом графике). Вам следует адаптировать этот диапазон к своим собственным потребностям. Например, на дневном графике можно выбрать диапазон от 5 до 20 дней. Но, повторюсь: вносите изменения, проводите бэктестинг, снова вносите изменения, снова проводите бэктестинг и, наконец, оптимизируйте. Если период полураспада бесконечен (невозвратный), отбросьте эту пару.
Интерпретация
Период полураспада выражается в количестве периодов, соответствующих данному временному интервалу. В нашей примерной стратегии это четырехчасовой период, поскольку мы проводим тестирование на таймфрейме H4. Таким образом, период полураспада, равный 10, означает, что для уменьшения отклонения диапазона от среднего значения на 50 % требуется примерно 10 баров или 40 часов.
Например, на рисунке 3 показана таблица coint_rank без каких-либо фильтров (за исключением условия half_life IS NOT NULL). В первых двух строках приведены записи для одной и той же корзины, периода и периода обратного просмотра, полученные в два разных момента времени (с отметками времени). Измеренный период полураспада составляет ~6,98, что означает: при проверке этой корзины на коинтеграцию по методу Йохансена на таймфрейме H4 с периодом обратного анализа в 180 дней требуется почти 7 баров по 4 часа каждый, или 48 часов, чтобы отклонение спреда вернулось к среднему значению. Подразумевается, что в течение остальных 48 часов спред увеличился от среднего значения до своего пика.

Рис. 3. Интерфейс базы данных Metaeditor, отображающий таблицу coint_rank с полем half_life, имеющим ограничение NOT NULL
На рисунке 4 видно, что в той же таблице приведен пример очень длительного периода полураспада. В данном случае, это один из самых высоких показателей среди зафиксированных результатов тестирования. При анализе данного корзины с периодом обратного обзора в 120 дней требуется почти 934 бара по 4 часа каждый, или почти 155 дней, чтобы спред вернулся к среднему значению. Очевидно, что вам не стоит торговать этой корзиной, поскольку это потребует более длительного удержания позиций, что увеличит нашу подверженность рыночным рискам.

Рис. 4. Интерфейс базы данных Metaeditor, отображающий таблицу coint_rank с полем half_life, значением которого является MAX, а не infinity
Обратите внимание на использование условия NOT IN для исключения значений «Infinity» в SQLite. Без этого условия возвращаемое значение периода полураспада MAX будет равно «Бесконечность». В данном случае можно предположить, что спред никогда не вернётся к среднему значению; иными словами, наши позиции могут оставаться открытыми на неопределённый срок, если у нас нет других критериев закрытия или стоп-лосса, таких как, например, пороговое значение по времени удержания позиции (закрытие по времени).

Рис. 5. Интерфейс базы данных Metaeditor, отображающий таблицу coint_rank с максимальным значением half_life без фильтра «бесконечность»
Бесконечный период полураспада указывает на отсутствие возврата к среднему значению (спред является нестационарным или напоминает случайное блуждание). Наш код сохраняет это значение как `np.inf`, что указывает на то, что данная корзина не подходит для торговли на возвращении к среднему значению на таймфрейме H4 с периодом анализа в 180 дней.
def compute_half_life(self, spread: pd.Series) -> float: """ Compute the half-life of mean reversion for a given spread series. Parameters ---------- spread : pd.Series The stationary spread (residual) series from the cointegration vector. Returns ------- float The half-life in periods (e.g., bars). Returns np.inf if no mean reversion (beta >= 0), np.nan if insufficient data. """ if len(spread) < 3: return np.nan lag = spread.shift(1).dropna() delta = (spread - lag).dropna() if len(delta) < 2: return np.nan res = sm.OLS(delta, sm.add_constant(lag, has_constant='add')).fit() # beta = res.params[1] beta = res.params.iloc[-1] if beta >= 0: return np.inf else: return -np.log(2) / beta
Напротив, но в тех же самых выборочных данных мы имеем очень короткий период полураспада — минимальный табличный период полураспада, равный почти полутора барам H4, или примерно 6 часов до возврата к среднему.

Рис. 6. Интерфейс базы данных Metaeditor, отображающий таблицу coint_rank с полем MIN half_life
Это идеально подходит для статистического арбитража, поскольку предполагает, что торговые возможности (открытие позиции при отклонении спреда и закрытие при его возвращении к исходному значению) могут возникать чаще в течение короткого периода времени.

Рис. 7. Capture_metaeditor_db_coint_rank_half_life_NULL
Наконец, на рисунке 7 видно, что у нас тысячи значений NaN (Not a Number) для параметра Half-Life. SQLite сохраняет их в виде значений NULL; они возникают при недостатке данных или при наличии числовых проблем, таких как постоянное расхождение, что указывает на сбой вычислений.
Подводя итог, можно сказать, что с точки зрения прибыльности более короткий период полужизни, как правило, более выгоден для стратегий статистического арбитража, поскольку он предполагает более быстрые сделки с меньшей замороженностью капитала и сниженным риском нарушения коинтеграции; однако всегда следует учитывать, что понятия «короткий» или «длинный» период полужизни носят относительный характер, поскольку они должны соответствовать нашим стратегическим критериям оценки. В нашем примере это будет короткая или длинная позиция относительно таймфрейма H4, что является нашим стратегическим критерием с самого начала оценки.
Имейте в виду, что:
- Предположение о конечной периоде распада предполагает, что спред является стационарным. Проверьте показатель `stability_stat` (статистика ADF), чтобы убедиться, что спред остается стационарным вне выборки. Меньше отрицательное значение `stability_stat` указывает на то, что вектор коинтеграции может не сохраниться в будущем.
- Такие проблемы, как постоянные или практически постоянные ценовые ряды, могут приводить к появлению значений `NaN` или `inf`. Это свидетельствует о низком качестве данных или низкой ликвидности.
- Период полураспада зависит от временного интервала. На другом временном интервале у той же корзины может наблюдаться другой период полураспада. Сравните периоды полураспада в одном и том же временном интервале.
Скрипт coint_ranker_auto.py, приложенный к этой статье, поможет понять поведение периода полураспада, построив график разброса значений для ведущих корзин, что позволит визуально убедиться в том, что скорость возврата соответствует рассчитанному периоду полураспада.

Рис. 8. График коинтегрированного спреда для корзины из четырёх акций Nasdaq с периодом полураспада возврата к среднему значению
В нашей системе скоринга мы будем использовать показатель «период полураспада» наряду с показателями rank_score и stability_stat (см. ниже) для ранжирования корзин. Мы будем отдавать приоритет корзинам с высоким показателем rank_score (сильная коинтеграция), коротким показателем half_life (быстрое возвращение к среднему значению) и более отрицательным показателем stability_stat (стабильный вектор).
Стабильность весов в портфеле
Веса в портфеле являются основой статистического арбитража на основе коинтеграции. Они будут определять не только размер каждой одновременно открытой хеджируемой позиции, но и их направление. Мы уже подробно говорили о них при рассмотрении теста коинтеграции Йохансена, поэтому не будем повторяться, чтобы сэкономить ваше время. Достаточно помнить, что именно они лежат в основе расчета спреда с возвратом к среднему значению.
Они получены из собственных векторов, вычисленных с помощью теста Йохансена — многомерного статистического метода, который мы использовали для выявления коинтеграции между несколькими рядами цен акций (нестационарными временными рядами). Этот тест позволяет оценить количество коинтегрирующих векторов и вычислить соответствующие собственные векторы, представляющие собой линейные комбинации активов, составляющих стационарный торгуемый портфель.
Например, в нашем последнем бэктесте для корзины, состоящей из акций «NVDA», «INTC», «AVGO» и «ASX», мы использовали значения {1,0; -1,9598590335874817; -0,36649674991957104; 21,608207065113874} в качестве весов портфеля.
| NVDA | INTC | AVGO | ASX |
|---|---|---|---|
| 1,0 | -1,9598590335874817 | -0,36649674991957104 | 21,608207065113874 |
Таблица 1. Корзина акций с соответствующими весами в портфеле (векторами коинтеграции)
Это означает, что на каждую единицу длинной позиции по NVDA мы будем открывать
- Одна короткая позиция в размере ~1,96 акций INTC
- Одна короткая позиция в размере ~0,37 единиц AVGO
- Еще одна длинная позиция в размере ~21,6 единиц ASX
Именно эта схема взвешенного хеджирования в идеале обеспечивает ожидаемую рыночную нейтральность статистического арбитража. (В идеале, поскольку рыночная нейтральность — это цель, которой никогда не удается достичь полностью.) (Остаточные риски всегда будут существовать, но они совершенно не входят в рамки данного обсуждения.) Однако на динамичных финансовых рынках предположение о том, что эти веса остаются неизменными во времени, может быть рискованным. При оценке необходимо проверить устойчивость этих собственных векторов Йохансена по крайней мере по трем причинам:
Во-первых, поскольку коинтеграция подразумевает долгосрочное равновесие, а экономические режимы меняются под влиянием таких факторов, как изменения в политике, обвалы рынков или ротация секторов. Если собственные векторы значительно колеблются во времени в рамках скользящих окон оценки, это свидетельствует о том, что лежащая в основе зависимость, возможно, не является стабильной. Нестабильные веса могут привести к сбою механизма возврата к среднему значению. Они могут превратить прибыльную арбитражную стратегию в стратегию, подверженную постоянным отклонениям и убыткам.
Во-вторых, поскольку стабильные веса гарантируют, что портфель остается застрахованным от влияния общих факторов, таких как, например, рыночная бета. Нестабильность может увеличить нашу подверженность непреднамеренным рискам, таким как всплески волатильности или нарушение коинтеграции. Например, во время финансового кризиса 2008 года многие коинтегрированные пары финансовых акций перестали быть взаимосвязанными, в результате чего прежние собственные векторы утратили свою актуальность. Регулярная проверка стабильности помогает избежать чрезмерной уверенности в исторических оценках и побуждает своевременно корректировать стратегию, например, пересматривать веса или закрывать позиции. Это главная причина, по которой мы внедрили концепцию «базы данных как единственного достоверного источника информации» для обновления моделей в режиме реального времени.
И в-третьих, потому что нестабильные веса в портфеле почти наверняка приведут к более значительным просадкам и снижению коэффициента Шарпа. Если будет обнаружена нестабильность, мы должны резко понизить рейтинг корзины в нашей системе оценки.
Что такое `stability_stat`?
Как следует из названия, поле stability_stat в таблице coint_rank хранит статистику стабильности весов портфеля. Его значение можно рассчитать двумя различными способами:
Сравнение собственных векторов скользящих окон — это прямой метод, при котором мы сравниваем вектор коинтеграции по последовательным скользящим окнам. Мы ожидаем, что косинусное расстояние будет оставаться в пределах заданного порогового значения, чтобы считать веса портфеля стабильными.
Проверка с помощью ADF на данных выборки и вне выборки — это четырехэтапный процесс. Для начала мы разбиваем данные о ценах на две части — для тестов внутри выборки (IS) и вне выборки (OOS) — и вычисляем вектор коинтеграции по данным IS. Затем, используя этот вектор коинтеграции, мы рассчитываем спред по данным вне периода. В заключение мы оцениваем статистику ADF по этому разбросу данных, выходящих за пределы диапазона (OOS). Мы ожидаем, что показатели IS и OOS ADF будут свидетельствовать о стационарности, чтобы считать веса портфеля стабильными.
То есть:
- Разделить данные на внутривыборку и вневыборку
- Получить статистику вектора коинтегрированности, дисперсии и ADF внутривыборки
- Используйте тот же вектор коинтеграции для расчета показателей разброса и ADF на вневыборочной выборке
Эти статистические показатели ADF представляют собой тот же тест стационарности Дики-Фуллера с расширенными параметрами, который мы используем с самого начала этой серии. Как уже упоминалось, мы уже рассказывали о тесте ADF, и вам, внимательному читателю, предлагаем ознакомиться со статьёй, в которой мы представили этот тест.
Не существует «лучшего» или рекомендуемого метода. Оба метода полезны для разных целей. Сравнение по собственным векторам с использованием скользящих окон лучше подходит для обновления моделей в режиме реального времени и перебалансировки портфеля при мониторинге онлайн-торговли. Внутривыборочная/вневыборочная проверка ADF лучше подходит для сценариев, предшествующих торговле, таких как этап оценки, а также для оценки жизнеспособности портфеля при бэктестировании. Именно этот метод мы должны использовать для оценки стабильности портфеля. Итак, именно этот метод мы используем здесь, в нашей системе оценки.
(Учитывая важность постоянной переоценки весов портфеля для защиты счета, в конечном итоге мы планируем опубликовать статью с результатами сравнительного анализа применения этих двух методов в различных сценариях.)
В прилагаемом скрипте на языке Python вы найдёте функцию compute_stability, которую мы используем. Как уже было сказано, он основан на втором из приведенных выше методов.
1. Разделить данные о ценах на внутривыборочный (по умолчанию первые 70 %, регулируется параметром `split_ratio=0.7`) и вневыборочный (остальные 30 %) периоды.
def compute_stability( self, prices: pd.DataFrame, method: str, split_ratio: float = 0.7, det_order: int = 0, k_ar_diff: int = 1 ) -> float: """ Check the stability of the cointegration vector by computing it on in-sample data and evaluating the ADF statistic on the out-of-sample spread. Parameters ---------- prices : pd.DataFrame DataFrame with columns as asset tickers and rows as time-indexed prices. method : str 'Engle-Granger' for pairs or 'Johansen' for baskets. split_ratio : float Fraction of data to use as in-sample (default 0.7). det_order : int Deterministic order for Johansen (default 0). k_ar_diff : int Lag order for Johansen (default 1). Returns ------- float The out-of-sample ADF statistic (more negative indicates stronger stability/stationarity). Returns np.nan if insufficient data. """ n = len(prices) if n < 50: return np.nan split = int(n * split_ratio) if n - split < 30: return np.nan train = prices.iloc[:split] test = prices.iloc[split:]
2. Вычислите вектор коинтеграции с помощью функции `get_coint_vector` на данных внутри выборки (используя либо модель Энгл-Грейнджера для пар, либо модель Йохансена для корзин).
try:
vec_is = self.get_coint_vector(train, method, det_order, k_ar_diff) 3. Примените этот вектор к ценам вне выборки, чтобы получить спред
spread_oos = pd.Series(np.dot(test.values, vec_is), index=test.index) if spread_oos.std() < 1e-10 or spread_oos.isnull().any(): self.logger.debug("Out-of-sample spread is effectively constant or contains NaNs") raise ValueError("Out-of-sample spread is effectively constant or contains NaNs")
4. Запустите тест ADF для этого вневыборочного спреда с помощью функции `adfuller` из пакета `statsmodels`, извлекая статистику теста (первый элемент результата, `adfuller(spread_oos)[0]`)
adf_stat = adfuller(spread_oos)[0] 5. Возвращает статистику ADF в качестве `stability_stat` или `np.nan`, если вычисление завершилось с ошибкой (например, из-за недостаточного объема данных или численных проблем).
return float(adf_stat) except Exception as e: self.logger.warning(f"Stability computation error: {e}") return np.nan
Как вы, возможно, уже заметили, в конечном итоге мы просто оцениваем, насколько устойчива коинтеграционная зависимость при применении к данным, не входящим в выборку. Это статистика ADF, вычисленная для спреда на вневыборочных данных, которая проверяет, остается ли спред стационарным, когда вектор коинтеграции (полученный на основе внутривыборочных данных) применяется к неизвестным (новым) данным. Мы анализируем, сохранится ли эта коинтеграционная зависимость в будущем.
Следует помнить, что более отрицательное значение показателя ADF свидетельствует о более выраженной стационарности, то есть о более весомых признаках возврата к среднему значению в вневыборочном спреде. Типичные критические значения для ADF при уровне значимости 5 % находятся в диапазоне от -2,86 до -3,5 (в зависимости от размера выборки и модели). Если значение `stability_stat` меньше этих значений, то с вероятностью 95 % разброс, скорее всего, является стационарным.
С другой стороны, значения, близкие к нулю или положительные, указывают на то, что спред вне выборки, вероятно, является нестационарным, что говорит о том, что вектор коинтеграции не порождает спред, возвращающийся к среднему значению, на неизвестных (новых) данных. Это означает, что коинтеграционная зависимость является нестабильной и может оказаться недостоверной для торговли.
Для применения этого метода требуется достаточное количество данных, не входящих в выборку. Здесь мы требуем не менее 30 баров (n — split < 30). При анализе данных за последние 180 дней на таймфрейме H4 (около 1080 часов реальной торговли на биржах, около 270 баров) период вне выборки составляет около 81 бара. Более короткие периоды обратного просмотра могут снизить надежность.
Обратите внимание, что показатель stability_stat зависит от периода (в нашем случае — H4). Конфигурация «корзины» на графике H4 может отсутствовать на графиках D1 или M15, поэтому сравнивайте данные в рамках одного и того же временного интервала. Кроме того, следует учитывать, что одной только стабильности весов в портфеле недостаточно. Корзина с очень низким показателем stability_stat, но длительным периодом полураспада может быть стабильной, но слишком медленной для практической торговли. Мы всегда должны учитывать его в сочетании с периодом полураспада и p-значением (модель Энгл-Грэнджера) или показателем собственной силы (модель Йохансена).
В нашей таблице coint_rank мы отдаем приоритет корзинам, у которых показатель stability_stat имеет значение, более отрицательное, чем -2,86 при уровне значимости 5 %, и объединяем его с показателями half_life и p_value для парной торговли или с показателями half_life и eigen_strength для корзин.
Ниже приведены результаты применения этих фильтров к корзинам акций с использованием показателя eigen_strength из теста коинтеграции Йохансена.
SELECT timeframe as tf, lookback as lb, assets as basket, coint_vector as weights, eigen_strength as strength, stability_stat as stability, half_life as rev_bars FROM 'coint_rank' WHERE stability_stat < -2.86 ORDER BY eigen_strength DESC;

Рис. 9. Интерфейс базы данных Metaeditor, отображающий таблицу coint_rank с выбранными корзинами для бэктеста
Мы выбираем корзины с наибольшей степенью коинтеграции из тех, у которых показатели стабильности ниже нашего порогового значения -2,86. Светло-голубым цветом мы выделили пять лучших вариантов для рассмотрения. Обратите внимание, что у первых четырёх из пяти лидеров есть по крайней мере один вес (то есть один вектор коинтеграции), который является исключительно высоким по сравнению с нормализованным значением (1,0).
Возьмём для примера третью корзину:
| Тикер (символ) | AVGO | LAES | MRVL | ASX |
|---|---|---|---|---|
| Средневзвешенная стоимость портфеля | 1,0 | 47,6 | -3,1 | -78,4 |
Таблица 2. Корзина акций с очень высокими требованиями к коротким продажам
Это означает, что на каждую акцию AVGO в нашей позиции нам придётся купить 47 акций LAES и одновременно продать 3 акции MRVL и 78 акций ASX. Как мелким розничным инвесторам нам следует избегать такого рода портфелей, поскольку, если у нас нет значительных средств и быстрого доступа (имеется в виду «приоритетный» доступ) к нашему брокеру, мы можем столкнуться с трудностями и высокими транзакционными издержками при попытке открыть короткую позицию по такому объему акций с относительно низкой ликвидностью. Опять же, «низкая ликвидность» в данном случае относится к акциям с самой высокой ликвидностью в полупроводниковой отрасли, таким как, например, NVDA, INTC и AVGO.
Мы бы предпочли выбрать более однородный портфель, например, такой, как в пятой строке.
| Тикер (символ) | INTC | WOLF | NVTS | ASX |
|---|---|---|---|---|
| Средневзвешенная стоимость портфеля | 1,0 | -0,6 | 1,46 | -0,46 |
Таблица 3. Портфель акций с одинаковыми весами в портфеле
Незначительное снижение силы коинтеграции с лихвой компенсируется хорошими показателями стабильности (-3,72). Кроме того, период возврата к среднему значению оценивается всего по трем барам H4, или почти двенадцати часам, что идеально соответствует ожидаемому времени удержания позиции в рамках нашей стратегии свинг-трейдинга на основе статистического арбитража.
Чтобы избежать такого дисбаланса в наших корзинах с наивысшим рейтингом, мы можем отфильтровать тикеры, которые его вызывают: LAES и ASX. Обратите внимание, что в таблице 3 ASX не является причиной дисбаланса. Это связано с тем, что он сильно несбалансирован в отношении NVDA, но мы не хотим исключать NVDA, поскольку он является частью нашей основной стратегии, нашей исходной гипотезы.
SELECT timeframe as tf, lookback as lb, assets as basket, coint_vector as weights, eigen_strength as strength, stability_stat as stability, half_life as rev_bars FROM 'coint_rank' WHERE stability < -2.86 AND basket NOT LIKE '%LAES%' AND basket NOT LIKE '%ASX%' ORDER BY strength DESC;

Рис. 10. Интерфейс базы данных Metaeditor, отображающий таблицу coint_rank с выбранными корзинами для бэктеста, исключающими тикеры LAES и ASX
Теперь в пятерке лидеров представлены более сбалансированные портфели. В частности, во второй строке показана перспективная корзина (без NVDA), с высокой стабильностью коинтеграции (−4,29) и оценочным временем возврата к среднему (half-life) около 48 часов (12 × H4), что идеально соответствует нашему свинг-трейдингу.
Давайте проведем бэктест и посмотрим, удалось ли нам добиться какого-либо улучшения по сравнению с предыдущим бэктестом, выполненным без учета этих двух дополнительных критериев ранжирования. (Позже мы сможем проверить это с помощью NVDA в третьей строке.)
Проведите бэктестинг
Как и в предыдущем бэктесте, мы жестко задаем торговые параметры в нашем советнике для проведения бэктестинга. Чтобы понять, почему, ознакомьтесь с результатами бэктеста, приведенными в предыдущей статье.
// check if we are backtesting if(MQLInfoInteger(MQL_TESTER)) { Print("Running on Tester"); ArrayResize(symbols, 4); ArrayResize(weights, 4); // "H4",120,"INTC,AMD,AVGO,MU" // "[1.0, -0.5005745550348311, 0.7458706676501435, -0.3108921739081775]" symbols[0] = "INTC"; symbols[1] = "AMD"; symbols[2] = "AVGO"; symbols[3] = "MU"; //--- weights[0] = 1.0; weights[1] = -0.5005745550348311; weights[2] = 0.7458706676501435; weights[3] = -0.3108921739081775; timeframe = PERIOD_H4; //InpLookbackPeriod = 120; } else { // Load strategy parameters from database if(!LoadStrategyFromDB(InpDbFilename, InpStrategyName, symbols, weights, timeframe, InpLookbackPeriod))
Обратите внимание, что мы включили период обратного просмотра в качестве возможного параметра, задаваемого пользователем (InpLookbackPeriod). Отныне мы разделяем период обратного анализа, используемый для расчета среднего спреда и стандартного отклонения (при открытии и закрытии позиций), и период обратного анализа, используемый для оценки коинтеграции. Кроме того, теперь в наших оптимизациях можно использовать среднее значение и стандартное отклонение за прошедший период, как это было сделано в данном случае.

Рис. 11. Снимок экрана, демонстрирующий входные данные, использованные в бэктесте
Ниже приведены результаты оптимизации, отсортированные по коэффициенту Шарпа.

Рис. 12. Снимок экрана с результатами оптимизации бэктеста, отсортированными по коэффициенту Шарпа
Мы провели один тест на втором устройстве, выделенном бледно-голубым цветом. По сравнению с первой строкой у этого второго места коэффициент Шарпа несколько ниже (6,87). Однако это с лихвой компенсируется более высоким коэффициентом восстановления (5,72), меньшей просадкой (3,36) и более чем двукратным превышением ожидаемой прибыли, причем почти половина сделок приносит прибыль, что означает более низкие транзакционные издержки.
Обратите внимание, что в ходе этого этапа оптимизации открытие позиций происходит при отклонении от среднего значения на 3,0 стандартных отклонения, а закрытие — при отклонении на 0,8 стандартных отклонения. Кроме того, мы проводим ретроспективный анализ с использованием 120-дневного периода коинтеграции и 90-дневного периода для расчета среднего значения спреда и стандартного отклонения.
Бэктест охватывал период в четыре месяца.

Рис. 13. Снимок экрана с настройками, использованными в бэктесте
Консолидированный отчет демонстрирует обнадеживающие показатели.

Рис. 14. Скриншот, демонстрирующий статистику сводного отчета по бэктесту
Обратите внимание на низкое качество исторических данных и сосредоточьтесь на основах статистического арбитража, а не на этих конкретных результатах.
Помимо статистических данных, уже упомянутых выше, обратите внимание на соотношение прибыльных сделок (51,67 %) и убыточных сделок (48,33 %). Это небольшое преимущество является характерной чертой статистического арбитража.
График баланса/собственного капитала демонстрирует устойчивую динамику кривой капитала с небольшими падениями.

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

Рис. 16. Скриншот, на котором показаны сроки открытия позиций в ходе бэктеста: время, дни и месяцы
Теперь наши сделки равномерно распределены по всем дням недели и всем четырём выбранным месяцам. Единственное сосредоточение внимания наблюдалось во время торговой сессии (США), что было ожидаемо, поскольку мы проводили бэктестинг акций Nasdaq.
В распределении прибыли по показателям MFE и MAE прослеживается определенный потенциал для улучшения.

Рис. 17. Скриншот, демонстрирующий распределение прибыли по показателям MFE и MAE в ходе бэктеста
Вероятно, мы сможем добиться лучших результатов, если будем применять динамическую перебалансировку портфеля — именно этому будет посвящена наша следующая статья.
И, наконец, главный вопрос, который поднимался в нашем предыдущем бэктесте: сроки удержания позиций.

Рис. 18. Скриншот, демонстрирующий сроки удержания позиций в рамках бэктеста
Максимальное и среднее время удержания позиции позволяют предположить, что включение показателя времени возврата к среднему значению в нашу систему оценки способствовало общему улучшению результатов, которое мы наблюдаем в данном случае. Раньше средний срок удержания позиции составлял около месяца, при этом по крайней мере одна позиция оставалась открытой более пятнадцати недель. В настоящее время средний срок составляет около 48 часов, и только три позиции оставались незаполненными более недели. Классическая свинг-торговля!
Заключение
В данной статье мы описали, реализовали и провели бэктестинг двух критериев ранжирования для упрощенной системы оценки, предназначенной для ранжирования корзин акций с целью статистического арбитража на основе коинтеграции. Эти два критерия являются частью системы оценки, которую мы начали описывать и разрабатывать в предыдущей статье. То есть два критерия, описанные здесь, и три других, описанных в предыдущей статье, составляют единую систему оценки, которую следует применять одновременно при отборе корзин акций для бэктестинга.
Первым описанным критерием является стабильность вектора коинтеграции, который оценивает стабильность весов портфеля во времени. Поскольку веса портфеля определяют его хеджирование, достижение желаемой рыночной нейтральности стратегий статистического арбитража в значительной степени зависит от их стабильности. Этот критерий помогает отсеять корзины, колебания которых выходят за пределы установленного порога.
Второй представленный здесь критерий — время возврата к среднему значению (период полуугасания), которое отражает среднюю продолжительность удержания позиции. Этот критерий напрямую связан со стратегическими критериями, описанными в предыдущей статье, где мы определили, будем ли мы работать в краткосрочной или долгосрочной перспективе, то есть хотим ли мы торговать в течение дня, на дневных графиках или в более длительных временных рамках. Учитывая предполагаемое время возврата к среднему значению, мы избегаем включения корзин, выход из которых с рынка потребовал бы чрезмерно много времени, тем самым удерживая нашу подверженность риску в допустимых пределах.
В заключение мы провели бэктест с использованием этих двух критериев в сочетании с тремя ранее описанными, и его результаты показывают, что мы можем добиться гораздо более эффективного отбора активов в портфель, если в систему оценки включить такие показатели, как время до возврата к среднему значению и стабильность весов портфеля.
Здесь мы приводим все файлы, необходимые для воспроизведения эксперимента, включая настройки бэктеста (файл *.ini) и конфигурацию оптимизации (файл *.set).
| Файл | Описание |
|---|---|
| config\CointNasdaq.INTC.H4.20250701_20251031.020.ini | Настройки конфигурации бэктеста |
| config\CointNasdaq.set | Файл SET с параметрами оптимизации |
| Experts\StatArb\CointNasdaq.mq5 | Пример советника |
| Файлы\StatArb\schema-0.5.sql | Обновленная схема базы данных (DDL) |
| Include\StatArb\CointNasdaq.mqh | Пример заголовка советника |
| Python\coint_ranker_auto.py | Класс Python для ранжирования и хранения результатов тестов на коинтеграцию |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/20173
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Разработка инструментария для анализа Price Action (Часть 34): Построение прогнозных моделей на основе необработанных рыночных данных с помощью усовершенствованного пайплайна загрузки данных
Моделирование рынка: Первые шаги на SQL в MQL5 (I)
Популяционные алгоритмы оптимизации: строим защиту от читеров
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Во-первых, я ценю вашу попытку просто объяснить эту тему.
Но я думаю, что ваши бэктесты далеки от реальности.
"Каждый тик" - это потому, что нам приходится иметь дело с очень низким качеством исторических данных для символов акций на демо-счете по умолчанию без подписки на биржу. 'Каждый тик' предоставляет хоть и низкокачественные, но все же немного лучшие исторические данные.
Если говорить о задержке 0, то это связано с тем, что в данной статье я сосредоточился на описании скелета предлагаемой скоринговой системы, а не на результатах работы стратегии в реальной торговле. Поэтому я даже не задумывался об этом.
Не поймите меня неправильно. Вы правы, и **ваше предупреждение читателям справедливо**. Эту "стратегию" следует читать между кавычками. Она никогда не задумывалась как реальная стратегия.
Спасибо.