Статистический арбитраж на основе коинтегрированных акций (Часть 5): Отбор активов
Введение
Мы начали эту серию статей с применения эвристического подхода, чтобы найти несколько акций компаний из полупроводниковой отрасли, которые — на момент написания статьи — демонстрировали высокую корреляцию с акциями NVDA (Nvidia Corporation). Затем мы отобрали несколько коинтегрированных акций, которые использовали при настройке нашей базы данных и в соответствующих примерах. Эти акции составляли наш «портфель»; он хорошо подходил для демонстрации и помогал понять весь процесс.
Теперь, когда мы внедрили сервис MetaTrader 5 для непрерывного обновления нашей базы данных, а наш советник также способен обновляться в режиме реального времени без перерывов, пришло время приступить к формированию реального портфеля коинтегрированных акций. Пора приступить к отбору.
«Скрининг — это процесс отбора из обширного набора акций или других финансовых инструментов тех, которые соответствуют конкретным критериям или параметрам, установленным инвестором или трейдером». «Этот процесс имеет решающее значение для сужения широкого спектра доступных вариантов и сосредоточения внимания на тех из них, которые соответствуют конкретной инвестиционной стратегии или торговой цели» (Investing.com)
Наша стратегия основана на возврате к среднему — это разновидность статистического арбитража, при которой выбираются акции компаний из индекса Nasdaq, коинтегрированные с акциями Nvidia, а затем одновременно осуществляется их покупка и продажа в соответствии с их весами в портфеле с целью обеспечения рыночной нейтральности. Наш обширный выбор включает в себя все акции, котирующиеся на Nasdaq. Необходимо соблюдать следующие конкретные критерии:
- Сила коинтеграции, определяемая с помощью тестов Энгл-Грейнджера и Йохансена
- Стабильность весов в портфеле
- Качество стационарности спреда, определяемое с помощью тестов ADF и KPSS
- Ликвидность активов
Этих четырёх критериев достаточно для того, чтобы мы могли разработать систему оценки. Система оценки должна дать нам торговое преимущество, основанное на выявлении групп акций, цены которых в долгосрочной перспективе изменяются синхронно, стабильно и предсказуемо. Нам нужна система оценки, поскольку не каждая пара или группа соответствует нашим требованиям. Если мы просто будем проверять все возможные комбинации акций — а их сотни или тысячи — количество потенциальных пар и корзин резко возрастёт. Не следует забывать, что мы разрабатываем эту платформу для статистического арбитража с учетом потребностей обычного розничного трейдера, располагающего обычным ноутбуком и стандартной пропускной способностью сети. Если мы начнём проверять все возможные комбинации, этот процесс с самого начала станет вычислительно затратным. Благодаря системе оценки мы избегаем риска вложения средств в пары или группы, которые коррелируют друг с другом лишь в краткосрочной перспективе или которые не подходят для торговли из-за низкой ликвидности или высоких транзакционных издержек.
Возможно, более подходящей аналогией для процесса отбора с использованием системы оценки будет воронка: мы начинаем с широкого круга кандидатов и постепенно отсеиваем неподходящих, сначала исключая их по сходству секторов и отраслей, а затем оценивая по показателям корреляции, коинтеграции и стационарности. Наконец, мы исключаем инструменты, которые непригодны для торговли с учётом наших правил управления риском и капиталом. Что повышает вероятность найти корзины, которые не только статистически значимы, но и экономически осмысленны и пригодны для торговли.
Скрининг и оценка позволяют преобразовать обширный список всех акций, доступных у нашего брокера, в целенаправленный набор перспективных вариантов. Это позволяет сэкономить время и ресурсы на таких же важных этапах, как бэктестинг и торговля на демо-счете.
На рис. 1 представлена блок-схема нашего процесса отбора.

Рис. 1. Концептуальная блок-схема процесса отбора
Определение исходной совокупности
Последний из перечисленных выше четырёх критериев — ликвидность активов — обычно является первым, к которому прибегают, начиная с нуля. Ликвидность будет влиять не только на транзакционные издержки (например, через спред), но и на саму пригодность к торговле, в зависимости от стратегии. Поскольку наша стратегия зависит от доступности акций для коротких продаж, ликвидность является отсекающим критерием.
Можно начать с крупного индекса, такого как S&P& 500 или Nasdaq-100, который уже обеспечивает отбор по критериям ликвидности и значимости на рынке. Или мы могли бы создать собственную выборку, используя такие критерии, как минимальная рыночная капитализация, средний дневной объем торгов или узкие спреды между ценами спроса и предложения. Оба способа были бы приемлемы для определения этой исходной совокупности. Но моя главная забота здесь заключается в том, чтобы вы, читатель, заинтересованный в воспроизведении этого процесса с помощью прилагаемого примера кода, смогли сделать это максимально просто и понятно. Итак, я использую демо-сервер MetaQuotes в качестве источника данных, поскольку, как я полагаю, он доступен бесплатно всем пользователям MetaTrader 5. Насколько я могу судить, у демо-сервера MetaQuotes нет отдельного пути (см. ниже) для акций, входящих в индекс &S&P 500, или для акций, входящих в индекс Nasdaq-100. Вместо этого он отражает динамику всех акций, котирующихся на Nasdaq.
Рис. 2. Окно «Символы» в MetaTrader 5 с выделенным путем к Nasdaq/Stock
Если мы решим начать только с акций из индекса Nasdaq-100, нам потребуется составить тщательно отобранный список акций, который необходимо будет обновлять при изменении состава индекса и/или перераспределении весов. В таком случае наши примеры очень скоро устареют. Таким образом, для наших целей в данном случае индекс Nasdaq/Stock является идеальным выбором в качестве отправной точки, нашей исходной совокупности.
Это составляет более шести тысяч символов!
Рис. 3. Количество символов на сервере MetaQuotes-Demo по группам
Фильтрация по секторам и отраслям
После того как мы определили исходную совокупность, следующим шагом будет применение наших знаний о рынке, опыта и здравого смысла для сужения круга поиска. Из этого следует, что акции одного сектора или отрасли часто имеют общие факторы влияния, например, они могут входить в одну цепочку поставок или зависеть от процентных ставок центрального банка. Эти общие факторы повышают вероятность того, что цены на эти активы будут стабильно двигаться в одном направлении. Например, банки могут одинаково реагировать на изменения процентных ставок, в то время как на компании-производители полупроводников, о которых идет речь в данном случае, может влиять мировой спрос на микросхемы.
Это почти интуитивное понимание рынка и здравый смысл легли в основу нашей эвристики при выборе той первой корзины акций, коинтегрированных с Nvidia.
Мы хотим иметь отфильтрованные акции под рукой для анализа данных, поэтому будем хранить их вместе с соответствующими метаданными в нашей таблице «symbol». Но сначала добавим в нашу таблицу столбец «источник», поскольку и путь к Metatrader 5, и
Метаданные символа могут отличаться — и, скорее всего, будут отличаться — при смене брокера или сервера. При анализе данных от разных брокеров нам потребуется отфильтровать рыночные данные по источнику.
ALTER TABLE symbol ADD source TEXT CHECK(LENGTH(source) <= 50) DEFAULT ('MetaQuotes') NOT NULL; А также скрипт MQL5 (SymbolImporter.mql5) для импорта символов из указанного пути в базе данных.
Рис. 4. Диалоговое окно SymbolImporter в MetaTrader 5 с параметрами
Это позволит восполнить пробелы, которые мы оставили в базе данных при запуске, а именно: тип актива, биржу, отрасль и сектор. Кроме того, в вышеуказанном поле будет указан источник данных.
//+------------------------------------------------------------------+ //| Insert a symbol into the database | //+------------------------------------------------------------------+ bool InsertSymbol(int db_handle, string ticker, string source) { ResetLastError(); // Get symbol information string exchange = SymbolInfoString(ticker, SYMBOL_EXCHANGE); string asset_type = GetAssetType(ticker); string sector = SymbolInfoString(ticker, SYMBOL_SECTOR_NAME); string industry = SymbolInfoString(ticker, SYMBOL_INDUSTRY_NAME); string currency = SymbolInfoString(ticker, SYMBOL_CURRENCY_BASE); if(currency == "") currency = SymbolInfoString(ticker, SYMBOL_CURRENCY_PROFIT); // Prepare SQL insert statement (symbol_id is auto-generated by SQLite) string req = StringFormat( "INSERT INTO symbol(ticker, exchange, asset_type, sector, industry, currency, source)" " VALUES( '%s', '%s', '%s', '%s', '%s', '%s', '%s')", ticker, exchange, asset_type, sector, industry, currency, source); if(!DatabaseExecute(db_handle, req)) { printf("Failed to insert symbol: %d", GetLastError()); return false; } return true; }
Он попытается определить тип актива по пути.
//+------------------------------------------------------------------+ //| Determine asset type based on symbol characteristics | //+------------------------------------------------------------------+ string GetAssetType(string symbol_name) { ResetLastError(); // Simple asset type detection - you may want to enhance this string description = SymbolInfoString(symbol_name, SYMBOL_DESCRIPTION); string path = SymbolInfoString(symbol_name, SYMBOL_PATH); if(StringFind(path, "Forex") != -1) return "Forex"; if(StringFind(path, "Stock") != -1) return "Stock"; if(StringFind(path, "Index") != -1) return "Index"; if(StringFind(path, "Future") != -1) return "Future"; if(StringFind(path, "CFD") != -1) return "CFD"; if(StringFind(path, "Crypto") != -1) return "Cryptocurrency"; // Fallback based on symbol name patterns if(StringLen(symbol_name) == 6 && StringFind(symbol_name, "USD") != -1) return "Forex"; if(StringFind(symbol_name, ".", 0) != -1) return "Stock"; return "Other"; }
Если всё пройдёт успешно, в журнале на вкладке «Эксперты» должно появиться примерно следующее.
Рис. 5. Вкладка «Эксперты» в MetaTrader 5 с журналом SymbolImport после успешного импорта
А если вы проверите свою базу данных, то в ней должны быть все акции, котирующиеся на Nasdaq и доступные на этом сервере (в данном случае — MetaQuotes-Demo).
Рис. 6. Интерфейс базы данных Metaeditor с примером метаданных акций Nasdaq
Мы можем проверить общее количество импортированных биржевых кодов с помощью оператора SELECT count().
Рис. 7. Интерфейс базы данных Metaeditor, отображающий общее количество акций Nasdaq, импортированных в базу данных SQLite
Почти семьсот из них относятся к технологическому сектору.
Рис. 8. Интерфейс базы данных Metaeditor, отображающий общее количество акций компаний технологического сектора, котирующихся на Nasdaq
Однако лишь чуть более шестидесяти из них относятся к полупроводниковой отрасли.
Рис. 9. Интерфейс базы данных Metaeditor, отображающий общее количество акций компаний полупроводниковой отрасли, котирующихся на Nasdaq
Эти шестьдесят три акции компаний из сектора полупроводников, котирующиеся на Nasdaq, заслуживают нашего пристального внимания. В дальнейшем мы сможем расширить сферу деятельности, изучая пересечения между различными секторами и отраслями, но сейчас нам нужно сосредоточиться на тех из них, которые в большей степени склонны изменяться синхронно. Именно они заслуживают того, чтобы мы проводили постоянные статистические проверки.
Корреляционный анализ
Теперь, когда мы сузили наш первоначальный круг акций по секторам и отраслям, следующим шагом будет проверка того, действительно ли эти акции на практике движутся синхронно. Самый простой способ — вычислить коэффициенты корреляции. Мы используем коэффициент корреляции Пирсона с самого начала этой серии, и будем продолжать это делать. Коэффициент корреляции Пирсона быстро рассчитывается и легко интерпретируется, что делает его практичным инструментом для скрининга.
Мы подробно рассмотрели тест корреляции Пирсона во второй и третьей статьях этой серии. Нет смысла повторять эту информацию здесь. Если вы не знакомы с этой темой, рекомендуем ознакомиться с этими статьями. Стоит помнить только об одном:
Корреляция — это НЕ то же самое, что коинтеграция. Цены на акции, как правило, представляют собой нестационарные временные ряды, и нестационарные временные ряды могут быть сильно коррелированными в краткосрочной перспективе, но не коинтегрированными в долгосрочной перспективе.
Корреляционный тест Спирмена также широко используется и имеет четкое и конкретное применение в качестве дополнительной проверки, но пока мы не будем его рассматривать. Вместо этого мы рассмотрим корреляцию Спирмена позже как более подходящий вариант для этих конкретных случаев использования.
Мы сохраним результаты теста на корреляцию в специальной таблице нашей базы данных, которая, как и следовало ожидать, называется «corr_pearson».
-- corr_pearson definition CREATE TABLE corr_pearson ( tstamp INTEGER NOT NULL, ref_ticker TEXT CHECK(LENGTH(ref_ticker) <= 10) NOT NULL, corr_ticker TEXT CHECK(LENGTH(corr_ticker) <= 10) NOT NULL, timeframe TEXT CHECK(LENGTH(timeframe) <= 10) NOT NULL, lookback INTEGER NOT NULL, coefficient REAL NOT NULL, CONSTRAINT corr_pearson_pk PRIMARY KEY(tstamp, ref_ticker, corr_ticker) ) STRICT;
С технической точки зрения сохранять результаты этих предварительных вычислений не требуется. Но в статистическом арбитраже главное — это ДАННЫЕ. Мы рассчитываем коэффициенты корреляции между акциями компаний-производителей полупроводников и акциями Nvidia Corp. на таймфрейме H4 с периодом анализа в 180 дней. Но в скором времени мы будем рассчитывать этот показатель для других временных интервалов и периодов анализа. Вероятно, мы повторим этот процесс для других базовых символов, помимо NVDA. По мере развития нашего анализа данных у нас появится огромный объем обработанных данных для расчета коэффициента корреляции Пирсона, коинтеграции по Энглу-Гранджеру и Йохансену, а также, возможно, для других промежуточных расчетов.
Как вы понимаете, все эти вычисления предоставляют ценную информацию, которую нужно сохранить, перепроверить и использовать в дальнейшем. Итак, если мы серьезно относимся к статистическому арбитражу, нам следует приучить себя не принимать во внимание мимолетные данные и не отбрасывать результаты промежуточных вычислений. Давайте сохраним всё, что может пригодиться в будущем. А если вы ещё не убедились, просто подумайте об этих двух словах: МАШИННОЕ ОБУЧЕНИЕ. Мы туда обязательно доберемся, это точно. 😀
Рис. 10. Интерфейс базы данных Metaeditor с отображением полей таблицы «corr_pearson»
Вы заметите, что у нас снова составной первичный ключ, в котором теперь используются временная метка записи, базовый тикер и коррелированный тикер. Это позволяет нам определить точный момент времени, когда был рассчитан коэффициент корреляции для данной пары символов. Таким образом, мы можем снова без проблем рассчитать корреляцию для всех инструментов, но уже на другом таймфрейме.
Рис. 11. Интерфейс базы данных Metaeditor, отображающий показатель «corr_pearson» с двумя разными временными интервалами, отсортированными
Из приведенного выше рис. 11 легко видно, что за один и тот же период ретроспективы для некоторых инструментов корреляция увеличивается при переходе с таймфрейма H4 на D1, в то время как для других — уменьшается. Если расширить этот подход на несколько временных интервалов и периодов анализа, мы получим мощный инструмент для анализа. Кроме того, эти данные будут, в некотором смысле, уникальными для вас, поскольку именно вы будете выбирать, какие символы анализировать и с использованием каких параметров. Выводы, которые вы сможете сделать на основе анализа этих данных, могут дать вам преимущество в торговле, а могут и нет, но суть именно в этом.
Позже мы сможем проследить изменение корреляции для каждого символа с NVDA во времени. Здесь представлена динамика корреляции между Broadcom Inc. Динамика корреляции между AVGO и NVDA: от одного года (252-дневный период) до одного месяца (30-дневный период), с промежуточными значениями 180, 120, 90 и 60 дней. Поскольку мы ранжируем данные по коэффициенту, результаты ясно показывают, что D1 является наиболее подходящим таймфреймом для этой пары; что корреляция снижается по мере сокращения таймфрейма; и что она резко падает при использовании менее 90 периодов обратного просмотра. Это огромный объем информации, а мы пока лишь коснулись поверхности.
Примечание: За исключением годового периода ретроспективы (252), для удобства визуализации я использую округленные цифры для месячных периодов ретроспективного анализа.
Рис. 12. Интерфейс базы данных Metaeditor, демонстрирующий динамику показателя «corr_pearson» между компаниями Broadcom Inc. (AVGO) и Nvidia Corp. (NVDA) за период от года до месяца на дневном графике
Вы понимаете, насколько ценной может быть эта простая таблица соотношений? В качестве простого примера я исключил таймфрейм H4, оставив только дневные корреляции, и построил график. 
Рис. 13. График изменения коэффициента корреляции Пирсона между Broadcom Inc. (AVGO) и Nvidia Corp. (NVDA) за период от года до месяца на дневном графике
Хотя использование коэффициента корреляции в качестве предварительного фильтра является общепринятой практикой, это ничего не говорит о коинтеграции. Таким образом, нельзя предполагать, что те символы, которые слабо коррелируют с Nvidia, не будут коинтегрированы. Так в чем же заключается цель проверки корреляции? Это помогает понять закономерности колебаний во времени. Но если корреляция не является гарантией коинтеграции, как же мы можем использовать её на практике? Рассчитывая корреляцию на коротких таймфреймах и за короткие периоды ретроспективы, одновременно проверяя коинтеграцию на длинных таймфреймах и длительных периодах расчёта. Обратите внимание, что понятия «короткий» и «длинный» здесь являются относительными. Как и всегда в трейдинге, даже в трейдинге, основанном на статистике, универсального рецепта не существует. Вам следует проводить тестирование и эксперименты, а также бэктестирование и оценку, чтобы найти оптимальное сочетание параметров теста корреляции и теста коинтеграции для каждой конкретной пары или группы активов. Разумно используйте свои знания рынка, здравый смысл и вычислительные возможности. Протестируйте, оцените результаты и повторите цикл
Сам скрипт теперь реализован в виде специального класса на языке Python.
Рис. 14. Снимок экрана из редактора скриптов Python, на котором показаны методы классов
А теперь давайте посмотрим, как мы можем объединить тесты на коинтеграцию в нашем процессе отбора.
Тесты коинтеграции
Энгл-Грейнджер
Тест Энгл-Грейнджера прост и несложен. Это был первый тест на коинтеграцию, который стал популярным среди квантитативных трейдеров в 1980-х годах и остается таковым до сих пор, даже после появления теста Йохансена в следующем десятилетии (см. ниже). Как уже упоминалось выше, мы подробно рассмотрели тесты коинтеграции во второй и третьей частях этой серии, поэтому не будем повторяться, чтобы сэкономить ваше время. Теперь мы сосредоточимся на их внедрении и применении в процессе скрининга. Если вы еще не знакомы с этой темой, ознакомьтесь с этими двумя статьями.
Просто помните, что для двух активов (символов) обычно используется тест Энгл-Грэнджера. Проверяется, являются ли остатки линейной регрессии между двумя ценовыми рядами стационарными. Однако этот метод ограничен лишь единственной коинтеграционной связью, поэтому он работает только для парных сочетаний.
Как и в случае с коэффициентом корреляции Пирсона, мы сохраним результаты теста коинтеграции Энгл-Грейнджера в специальной таблице нашей базы данных под названием «coint_eg».
-- coint_eg definition CREATE TABLE coint_eg ( tstamp INTEGER NOT NULL, ref_ticker TEXT CHECK(LENGTH(ref_ticker) <= 10) NOT NULL, coint_ticker TEXT CHECK(LENGTH(coint_ticker) <= 10) NOT NULL, timeframe TEXT CHECK(LENGTH(timeframe) <= 10) NOT NULL, lookback INTEGER NOT NULL, pvalue REAL NOT NULL, test_stat REAL NOT NULL, crit_val_1 REAL NOT NULL, crit_val_5 REAL NOT NULL, crit_val_10 REAL NOT NULL, hedge_ratio REAL, is_coint INTEGER NOT NULL, CONSTRAINT coint_eg_pk PRIMARY KEY(tstamp, ref_ticker, coint_ticker) ) STRICT;
Поле «is_coint» имеет тип BOOLEAN, однако в SQLite нет отдельного типа данных «Boolean». Вместо этого он использует тип INTEGER (или INT) для хранения булевых значений в виде 1 или 0, принимая в качестве входных данных значения TRUE или FALSE.
«В отличие от большинства других реализаций SQL, в SQLite нет отдельного типа данных BOOLEAN. Вместо этого значения TRUE и FALSE (как правило) представляются в виде целых чисел 1 и 0 соответственно.» (Документация SQLite)
Рис. 15. Интерфейс базы данных Metaeditor с отображением полей таблицы «coint_eg»
На момент написания данной статьи из всех шестидесяти двух символов только компания Aeluma Inc. (ALMU) находится в состоянии коинтеграции с Nvidia Corp. (NVDA) на дневном графике за период ретроспективы в 365 дней.
Рис. 16. Интерфейс базы данных Metaeditor, отображающий поля таблицы «coint_eg» с одной акцией, прошедшей коинтеграционный анализ в NVDA (ALMU)
Как и в примере «corr_pearson» выше, вы снова заметите, что у нас составной первичный ключ, состоящий из временной метки вставки, тикера ссылки и коррелированного тикера. По той же самой причине. Имея временные метки теста на коинтеграцию, связанные с эталонным тикером и, в конечном итоге, с коинтегрированным тикером, мы можем повторно проверить коинтеграцию для всех символов, но теперь с другим временным интервалом и/или периодом обратного анализа.
Эта таблица, наряду с таблицей «coint_joh», представленной ниже, станет основой нашего процесса отбора. В качестве примера давайте рассмотрим возможности на часовом графике (H4).
Рис. 17. Интерфейс базы данных Metaeditor, отображающий таблицу «coint_eg» с семнадцатью акциями, коинтегрированными с акциями Nvidia Corp. (NVDA) на часовом графике и с различными периодами анализа
Таблица на рис. 17 отсортирована по p-значению. На момент написания статьи — первая из них, Silicon Laboratories Inc. (SLAB) демонстрирует наибольшую коинтеграцию с NVDA, если взять 30-периодный промежуток времени. Однако любая из этих акций является хорошим кандидатом для парной торговли с NVDA на 4-часовом графике в рамках соответствующих периодов обратного анализа. То есть на данный момент у нас уже есть солидный портфель коинтегрированных с NVDA акций для парной торговли с указанием соответствующих коэффициентов хеджирования. Если остановиться на этом, мы получим портфель, ранжированный по степени коинтеграции, который останется лишь проверить на стационарность спреда и подтвердить его эффективность в ходе бэктестирования.
Йохансен
Метод Йохансена был разработан сразу после появления критерия Энгл-Грейнджера, в конце 1980-х годов. В 1990-е годы этот метод стал стандартным инструментом в академических кругах, а в начале нынешнего века его постепенно стали использовать команды квантитативных аналитиков в хедж-фондах по всему миру.
В то время как тест Энгл-Грейнджера предназначен для двух — и только двух — временных рядов или двух активов, тест Йохансена предназначен для работы с несколькими временными рядами, включая, в том числе, и два временных ряда. Это делает данный подход особенно эффективным при анализе корзин из двух, трёх, четырёх или более акций, в которых могут сосуществовать несколько различных долгосрочных взаимосвязей.
Йохансен выводит два основных статистических показателя: след и максимальное собственное значение. Эти статистические показатели определяют степень коинтеграции, то есть количество независимых стационарных взаимосвязей между активами.
- Ранг = 0: коинтеграции нет.
- Ранг = 1: один стабильный спред. Мы рассматривали этот случай во всех статьях данной серии.
- Ранг >1: несколько независимых спредов, что обеспечивает большую гибкость при формировании портфеля. Мы подробно рассмотрим этот случай при изучении сезонности и аномалий.
Этот тест позволяет получить набор собственных векторов, связанных с этими стационарными соотношениями. Каждый из этих собственных векторов определяет веса линейной комбинации акций, которая остается неизменной во времени. Именно эти веса мы используем в качестве коэффициентов хеджирования, то есть в таких пропорциях мы будем комбинировать активы для формирования синтетического спреда, возвращающегося к среднему значению. Предоставляя нам эти коэффициенты хеджирования, тест показывает, как сформировать спред между коинтегрированными акциями. Мы использовали эту функцию во второй статье данной серии, когда проводили бэктестинг группы из четырёх акций компаний полупроводниковой отрасли.
Как и в случае с двумя предыдущими тестами, мы будем использовать специальную таблицу для хранения как параметров, так и результатов теста Йохансена для последующего анализа. Но теперь нам понадобятся две таблицы:
Первая таблица: основная таблица «coint_johansen_test» для хранения метаданных каждого прогона теста.
-- coint_johansen_test definition CREATE TABLE coint_johansen_test ( test_id INTEGER PRIMARY KEY AUTOINCREMENT, tstamp INTEGER NOT NULL, timeframe TEXT CHECK(LENGTH(timeframe) <= 10) NOT NULL, lookback INTEGER NOT NULL, num_assets INTEGER NOT NULL, trace_stats_json TEXT NOT NULL, trace_crit_vals_json TEXT NOT NULL, eigen_stats_json TEXT NOT NULL, eigen_crit_vals_json TEXT NOT NULL, coint_rank INTEGER NOT NULL, CONSTRAINT coint_johansen_test_unique UNIQUE (tstamp, timeframe, lookback) ) STRICT;
Вместо использования составного первичного ключа с тикерами активов, как это было сделано в таблицах для теста корреляции и теста коинтеграции Энгл-Грейнджера, теперь в качестве первичного ключа в основной таблице используется единый автоматически увеличиваемый идентификатор test_id. Затем в таблице coint_johansen_test_assets (ниже) сохраняется строка для каждого актива, включенного в данный тестовый прогон. Таким образом, мы можем тестировать любое количество объектов без изменения структуры таблицы. Если вы хотите глубже разобраться в этом процессе, ознакомьтесь со статьёй в Википедии, посвящённой нормализации баз данных.
Ограничение, требующее, чтобы временная метка теста, таймфрейм и период обратного просмотра были в совокупности уникальными, позволяет нам повторно запускать тест для тех же символов в любое время с использованием разных таймфреймов и периодов ретроспективы, при этом результаты каждого теста будут уникальными.
Тест Йохансена выдает несколько значений для статистических показателей «trace» и «max-eigenvalue», а также их критические значения. Хранение этих данных в отдельных полях, таких как trace_stat, больше не является возможным. Вместо этого мы будем использовать столбцы JSON (trace_stats_json, trace_crit_vals_json и т. д.) для хранения всего массива результатов в виде строки. Мы используем JSON-строки и поле TEXT для хранения критических значений и массивов статистики следа, поскольку в SQLite изначально отсутствует тип данных ARRAY. Подробнее о том, как мы решаем эту проблему, связанную с ограничением SQLite, а также о том , как мы считываем и анализируем эти JSON-строки в MQL5, читайте в предыдущей статье.
На следующем этапе, при тестировании на исторических данных группы акций с помощью этого автоматического отбора, мы рассмотрим, как можно эффективно использовать критические значения (trace_crit_vals_json). Пока что просто имейте в виду, что именно эти значения мы сравниваем со статистикой трассировки (trace_stats_json), чтобы получить количество коинтеграционных отношений на каждом уровне значимости (соответственно 1 %, 5 % и 10 %).
Булевого поля is_coint уже недостаточно. Основным результатом теста Йохансена для нескольких активов является количество коинтеграционных отношений, или ранг (r). В новом поле coint_rank будет храниться целое значение ранга.
Вторая таблица: «coint_johansen_test_assets», предназначенная для привязки каждого теста к конкретным активам, участвующим в тестировании.
-- coint_johansen_test_assets definition CREATE TABLE coint_johansen_test_assets ( test_id INTEGER NOT NULL, symbol_id INTEGER NOT NULL, CONSTRAINT coint_johansen_test_assets_pk PRIMARY KEY(test_id, symbol_id), CONSTRAINT coint_johansen_test_assets_test_fk FOREIGN KEY(test_id) REFERENCES coint_johansen_test(test_id) ON DELETE CASCADE, CONSTRAINT coint_johansen_test_assets_symbol_fk FOREIGN KEY(symbol_id) REFERENCES symbol(symbol_id) ON DELETE CASCADE ) STRICT;
В таблице `coint_johansen_test_assets` вместо поля `ticker` используется поле `symbol_id` для установления связи внешнего ключа с таблицей «symbol». Это гарантирует, что тестируемые нами ресурсы всегда присутствуют в нашей системе. Кроме того, в данном случае мы получаем некоторое повышение производительности без дополнительных затрат, поскольку для баз данных, как правило, более эффективно выполнять соединения по целочисленным ключам (symbol_id), чем по текстовым полям (ticker). На данный момент это не имеет значения и не является нашей приоритетной задачей, пока у нас нет работающей системы. Но стоит отметить, что мы за это не платим. 🙂
Наконец, помните, что наша таблица «market_data» уже связана с таблицей «symbol». Подключив результаты теста Йохансена к таблице «symbol», мы косвенно связываем их с нашими (будущими) «отборными» рыночными данными, что способствует обеспечению согласованности нашей базы данных.
Но, если вы следите за ходом рассуждений, у вас, возможно, возникнет вопрос: Так почему же мы не поступили так же с результатами теста на корреляцию Пирсона и теста на коинтеграцию Энгл-Грейнджера? Почему мы использовали «ticker», а не «symbol_id», при интеграции этих элементов с остальной частью нашей системы?
Есть две причины для такого выбора, одна из которых довольно своеобразна. Дело в том, что я предпочитаю, чтобы вы проводили тест корреляции Пирсона и тест коинтеграции Энгл-Грейнджера для парной торговли как можно проще, не беспокоясь о том, чтобы предварительно импортировать символы из Metatrader в базу данных.
Вторая причина заключается в том, что тест Йохансена станет нашим основным инструментом в автоматизированной системе, тогда как тест корреляции и тест Энгл-Грейнджера будут применяться только при ручной проверке, поскольку в нашем портфеле мы будем работать с корзинами акций.
Рис. 18. Интерфейс базы данных Metaeditor с отображением полей таблицы «coint_johansen_test»
На рис. 18 выше представлена таблица теста коинтеграции Йохансена со всеми полями и одним тестом с векторами коинтеграции, которые мы будем использовать в качестве коэффициентов хеджирования или весов портфеля. Это лучше видно на рисунке 19 ниже.
Рис. 19. Интерфейс базы данных Metaeditor, отображающий поля таблицы «coint_johansen_test» с одним положительным результатом теста на коинтеграцию, а также рекомендуемые коэффициенты хеджирования (веса портфеля)
Рис. 20. Интерфейс базы данных Metaeditor, отображающий таблицу «coint_johansen_test» с семью положительными результатами тестов на коинтеграцию с различными рангами коинтеграции на таймфрейме D1 и различными периодами обратного анализа
Здесь у нас уже есть солидный набор коинтегрированных акций компаний полупроводниковой отрасли для всех наиболее распространенных периодов анализа — от месяца до года, полугода, квартала и т. д. Каждая группа акций из одного и того же периода анализа может быть проверена на предмет включения в корзину акций.
Эти данные будут использоваться в нашей таблице «стратегии» для обновления нашей модели в режиме реального времени, как показано в предыдущей статье. Здесь изложена основа для бэктеста, который мы проведем в следующей статье.
Важно понимать, что тест Энгл–Грейнджера не утратил своей актуальности с появлением теста Йохансена. Оба подхода направлены на одно и то же — на то, движутся ли две или более цен в долгосрочной перспективе синхронно, — но используют для этого разные методы. Метод Энгл–Грейнджера проще и подходит для проверки нескольких активов одновременно, тогда как метод Йохансена более эффективен, когда требуется проверить несколько переменных одновременно. На практике часто следует использовать тот вариант, который лучше подходит к ситуации, а иногда проверять оба, чтобы быть более уверенным в результате.
Тест Энгл-Грейнджера
| ✔️Плюсы | ❌Минусы |
|---|---|
|
|
Таблица 1 — Маркированный список преимуществ и недостатков теста коинтеграции Энгл-Грейнджера
Тест Йохансена
| ✔️Плюсы | ❌Минусы |
|---|---|
|
|
Таблица 2 — Маркированный список преимуществ и недостатков теста коинтеграции Йохансена
Как правило, для быстрой проверки двух акций используйте тест Энгл-Грейнджера; для более тщательного анализа портфелей или более двух активов — тест Йохансена.
Последний шаг — проверить, действительно ли спред стационарен.
Проверка стационарности
После того как мы выявили коинтеграционную зависимость в группе акций для заданного временного интервала и периода анализа, последним шагом является проверка того, действительно ли этот спред является стационарным. Именно свойство стационарности гарантирует, что спред колеблется вокруг стабильного среднего значения и не смещается со временем. Этот шаг необходим, поскольку без стационарности мы не можем быть уверены в том, что произойдет возврат к среднему, а именно в этом заключается суть нашей стратегии; именно в этом и заключаются возможности для арбитража.
Двумя тестами, которые мы использовали для проверки стационарности спреда, являются ADF (расширенный тест Дики–Фуллера) и KPSS (тест Квятковского–Филлипса–Шмидта–Шина). Их совместный анализ обеспечивает надежность результатов: если оба теста подтверждают стационарность спреда. Когда эти два фактора не совпадают, это часто свидетельствует о нестабильном поведении, и нам следует проявить осторожность: это тревожный сигнал.
Как и во всех предыдущих тестах, у нас также есть специальная таблица «coint_adf_kpss» для хранения результатов тестов на стационарность.
-- coint_adf_kpss definition CREATE TABLE "coint_adf_kpss" ( test_id INTEGER NOT NULL, symbol_id INTEGER NOT NULL, adf_stat REAL NOT NULL, adf_pvalue REAL NOT NULL, is_adf_stationary INTEGER NOT NULL, kpss_stat REAL NOT NULL, kpss_pvalue REAL NOT NULL, is_kpss_stationary INTEGER NOT NULL, CONSTRAINT coint_adf_kpss_pk PRIMARY KEY (test_id, symbol_id), CONSTRAINT coint_adf_kpss_test_fk FOREIGN KEY (test_id) REFERENCES coint_johansen_test(test_id) ON DELETE CASCADE, CONSTRAINT coint_adf_kpss_symbol_fk FOREIGN KEY (symbol_id) REFERENCES symbol(symbol_id) ON DELETE CASCADE ) STRICT;
И снова мы используем составной первичный ключ, но теперь уникальность каждого теста обеспечивается не триплетом «метка времени запуска теста/временной интервал/период обратного просмотра», а связью с идентификатором теста Йохансена и символом (уникальность которых обеспечивается именно этим триплетом). Таким образом, мы связываем тесты стационарности с тестом Йохансена, но при этом делаем тест ADF/KPSS зависимым от того, содержит ли предыдущий тест на коинтеграцию хотя бы один коинтегрированный вектор.
Эта зависимость от предыдущего теста на коинтеграцию, в котором присутствует хотя бы один коинтегрированный вектор, явно отражена в коде на Python:
def get_coint_groups(self): """ Retrieves information for all Johansen tests with a cointegrating rank > 0. Returns a list of dictionaries, each containing test_id, timeframe, lookback, and a list of symbol_ids. """ if not self.conn: print("Database connection not established.") return [] try: query = """ SELECT test_id, timeframe, lookback FROM coint_johansen_test WHERE coint_rank > 0 """ df_tests = pd.read_sql_query(query, self.conn) if df_tests.empty: print("No cointegrated groups found in the database.") return []
Это осознанное архитектурное решение. Теоретически ничто не мешает нам запускать тесты ADF в качестве отдельного инструмента. Просто это не имеет смысла в нашем рабочем процессе. В нашем случае запуск модели только на коинтегрированной группе более экономичен по ресурсам, поскольку в скором времени мы будем запускать её круглосуточно на тысячах акций с различными комбинациями временных интервалов и периодов анализа.
Рис. 21. Интерфейс базы данных Metaeditor, отображающий таблицу «coint_adf_kpss» с одним положительным результатом для тестов стационарности спреда ADF и KPSS
По идентификатору теста (20) мы получаем символы, временной интервал, период ретроспективы, использованные в тесте коинтеграции Йохансена, а также векторы коинтеграции (веса портфеля), возвращаемые этим тестом.
SELECT
t1.test_id,
t3.ticker,
t2.timeframe,
t2.lookback,
t2.coint_vectors_json
FROM coint_johansen_test_assets AS t1
JOIN coint_johansen_test AS t2
ON t1.test_id = t2.test_id
JOIN symbol AS t3
ON t1.symbol_id = t3.symbol_id
WHERE t1.test_id = 123;
Это относительно дорогой запрос, содержащий две операции JOIN. Но давайте учтём, что мы будем выполнять небольшое количество таких запросов, и только в том случае, если найдём группу активов, прошедших все предыдущие фильтры. То есть этот запрос находится в конце нашего конвейера. Теперь у нас уже есть корзина акций с коинтеграцией и стабильным спредом, которую нужно скопировать в нашу таблицу «стратегий». Советник считывает данные из таблицы «strategy», чтобы обновлять свои собственные параметры в режиме реального времени (см. нашу предыдущую статью).
Рис. 22. Интерфейс базы данных Metaeditor, отображающий таблицу «coint_johansen_test» с подробными данными по конкретному идентификатору теста, для которого тесты ADF и KPSS дали положительные результаты
В данном примере, поскольку результаты тестов на стационарность ADF и KPSS для теста № 20 оказались положительными, мы знаем, что акции компаний AMD, INTC, LAES, RMBS, TSM и WOLF можно торговать на дневном (D1) таймфрейме, при этом соответствующие веса в портфеле сохранены в файле «coint_vectors_json».
На данный момент структура нашей базы данных значительно изменилась.

Рис. 23. Диаграмма «сущность-связь» (ERD), отражающая версию схемы statarb-0.3.
Возможно, вы заметили, что у нас пока нет индексов в базе данных. Это сделано намеренно. Поскольку мы решили использовать подход «снизу вверх», на данный момент мы можем лишь предположить (правда, с достаточной степенью уверенности), где нам понадобятся индексы для ускорения наиболее часто используемых и/или наиболее ресурсоемких запросов (например, запросов по составным первичным ключам, одна из пар которых представляет собой поле TEXT). Однако, чтобы избежать излишних размышлений и чрезмерной оптимизации, мы будем добавлять их только тогда, когда точно узнаем, какие запросы являются наиболее ресурсоемкими и/или наиболее часто используемыми.
Выбор корзины — создание системы подсчета очков
Группа акций, представленная на рис. 22 выше, представляет собой единую корзину акций, которая войдет в состав нашего портфеля. И, как ожидается, в нашем портфеле будет не менее дюжины таких корзин. Это связано с тем, что нам придется менять их местами; то есть нам нужно будет заменить корзину № 1 на корзину № 2 в любой момент, когда последняя окажется более перспективной с точки зрения ожидаемой доходности, чем первая. Нам необходимо иметь достаточно большой портфель, чтобы учитывать изменения рынка, корпоративные события и наши собственные торговые решения (например, прекращение торговли акциями компании X).
Мы провели всю эту фильтрацию, отбирая из совокупности примерно 10 000 активов — включая, в учебных целях, и другие типы активов помимо акций — узкую корзину из шести акций с конкретными временными рамками и коинтеграцией, подтверждающейся за определенный период ретроспективы. Мы подробно объяснили каждую деталь, чтобы вам было проще понять, но помните, что весь этот процесс будет работать в автоматическом режиме в режиме реального времени, проверяя множество комбинаций этих трех переменных (символа, таймфрейма и периода обратного анализа). Благодаря этому сочетанию можно ожидать, что у нас будет гораздо больше, чем одна корзина. Нам предстоит столкнуться с десятками корзин в очереди, ожидающих включения в наш торговый портфель. Как определить, какой из них будет следующим, а какой — последним в очереди? Ответ — система ранжирования.
Вот некоторые из критериев ранжирования, которые мы можем принять во внимание:
Степень коинтеграции — для оценки степени коинтеграции рядов можно использовать статистические показатели Йохансена (трас и максимальное собственное значение). Более сильные тестовые статистики свидетельствуют о более надежной долгосрочной зависимости.
Число коинтегрирующих векторов (ранг) — Учитывая число коинтегрирующих векторов, мы можем оценить желаемую сложность создаваемой системы.
- Ранг = 1 означает один стабильный спред. Проще контролировать.
- Ранг >1 означает наличие нескольких возможных диапазонов. Более гибкий, но и более сложный.
Стабильность весов в портфеле — мы можем избежать значительных колебаний коэффициентов хеджирования в разных временных интервалах выборки. Это может свидетельствовать о нестабильности отношений.
Разумные наценки — наценка должна быть экономически обоснованной. Следует избегать открытия чрезмерно крупных позиций по одной акции.
Время возврата к равновесию — мы можем рассчитать период полураспада возврата к среднему, чтобы оценить, как быстро спред возвращается к равновесию.
Ликвидность — ликвидность является незаменимым критерием, поскольку торговля с использованием спредов, включающих неликвидные акции, может обходиться дорого.
Транзакционные издержки — это весьма субъективный критерий ранжирования, поскольку он зависит от целей и торговой системы каждого конкретного участника, но мы включили его в список для полноты картины.
Мы можем создать систему оценки, объединив все или некоторые из вышеперечисленных критериев. Например, весовая оценка силы коинтеграции, стабильность весов в портфеле и средний спред. В конечном итоге, выбор корзины сводится к поиску баланса между статистической фильтрацией, знанием рынка и пригодностью к торговли.
Однако эта система скоринга имеет смысл только на практике. Поскольку наша следующая статья будет полностью посвящена бэктестированию создаваемой нами автоматизированной системы, мы начнем с разработки и тестирования системы оценки, основанной на некоторых из перечисленных выше критериев. Таким образом, мы будем тестировать саму систему подсчета очков, а также EA и различные варианты состава корзины.
Заключение
Мы рассмотрели, как можно реализовать упрощённую процедуру отбора активов для торговой стратегии, основанной на статистическом арбитраже с использованием коинтегрированных акций. Мы начали с обширного набора из тысяч символов, доступных на торговой платформе/сервере брокера, и путем последовательной фильтрации выделили одну корзину из шести акций с единым вектором коинтеграции и стационарным спредом.
Сначала мы выбрали наиболее коррелированные акции компаний полупроводниковой отрасли, чтобы сформировать окончательную корзину на основе теста коинтеграции Йохансена и тестов стационарности ADF/KPSS.
В ходе этой работы мы модифицировали нашу базу данных, добавив специальные таблицы для хранения результатов каждого теста с целью их дальнейшего анализа. Кроме того, мы разработали модульные классы на Python, чтобы заменить скрипты, которые мы использовали до сих пор.
На данном этапе мы готовы приступить к тестированию нашей системы оценки, чем и займёмся на следующем этапе, проводя бэктестинг нашей системы с использованием разработанного здесь метода отбора.
Небольшое личное замечание по поводу приложенного кода
Прилагаемый код, написанный преимущественно на языке Python, был создан с широким использованием встроенных AI-помощников в Metatrader 5, VS Code и других программах.
Я рассматриваю ИИ для разработки кода как самый высокоуровневый язык нашего времени. Это не избавляет от необходимости владеть навыками программирования. Напротив, это требует от разработчика четкого понимания системы, а также четкого понимания так называемых требований пользователей. Нужно очень хорошо понимать, о чём просить, как об этом просить, и, что самое главное, разработчик должен уметь проверять, соответствует ли предоставленный помощником код требованиям, то есть выполняет ли он то, что от него ожидается. В таком случае помощник будет выполнять функции младшего разработчика с «усиленными» возможностями или даже старшего разработчика — в зависимости от модели ИИ, заданных параметров и используемого языка программирования.
По аналогии с этим я рассматриваю ИИ в сфере разработки кода как создание языка программирования C: он не избавляет от необходимости владеть программированием, а лишь освобождает разработчика от необходимости писать код на ассемблере. ИИ-помощники уже способны писать более качественный код за меньшее время, причём тесты и документация предоставляются бесплатно.
В нашем случае относительно «низкоуровневым» языком программирования является Python или MQL5.
| Имя файла | Описание |
|---|---|
| Файлы\StatArb\schema-0.3.sql | Схема SQL (DDL) для базы данных SQLite версии 0.3 |
| Scripts\StatArb\db-setup.mq5 | Скрипт MQL5 для настройки базы данных (считывает указанный выше файл схемы) |
| Scripts\StatArb\SymbolImporter.mq5 | Скрипт MQL5 для импорта метаданных символов из MetaTrader 5 в базу данных SQLite |
| coint_adf_kpss_to_db.py | Класс на Python с методами для выполнения тестов стационарности ADF и KPSS |
| coint_eg_to_db.py | Класс на Python с методами для проведения теста коинтеграции Энгл-Грейнджера |
| coint_johansen_to_db.py | Класс Python с методами для выполнения теста коинтеграции Йохансена |
| corr_pearson_to_db.py | Класс Python с методами для выполнения теста корреляции Пирсона |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/19626
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Создание самооптимизирующихся советников на MQL5 (Часть 15): Идентификация линейных систем
Алготрейдинг без рутины: быстрый анализ сделок в MetaTrader 5 с SQLite
Создание прибыльной торговой системы (Часть 2): Тонкости управления размером позиции
Моделирование рынка (Часть 24): Первые шаги на SQL (VII)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования