English Deutsch 日本語
preview
Статистический арбитраж на основе коинтегрированных акций (Часть 6): Система оценки

Статистический арбитраж на основе коинтегрированных акций (Часть 6): Система оценки

MetaTrader 5Торговые системы |
95 0
Jocimar Lopes
Jocimar Lopes

Введение

Мы достигли первого важного этапа в создании полностью автоматизированной системы статистического арбитража на основе коинтегрированных акций. Мы разработали классы на Python для тестирования коинтеграции, создали и доработали исходную базу данных, а также подготовили советник, готовый к использованию для тестирования стратегий. С помощью небольшого фрагмента кода модель, определяющая параметры стратегии для советника, может даже обновляться в режиме реального времени за счет получения данных из базы данных. Это был долгий путь к внедрению нашей системы статистического арбитража для рядового трейдера. Мы даже разработали пошаговую методику отбора акций из всего спектра компаний, котирующихся на Nasdaq, до десятка ценных бумаг компаний, связанных с производством полупроводников. Однако у нас нет системы подсчета очков.

Учитывая рабочий процесс отбора, мы теперь готовы разработать систему оценки и провести её тестирование на исторических данных. В предыдущей статье были приведены общие положения о возможных критериях. 

  • Степень коинтеграции 
  • Число коинтегрирующих векторов (ранг) 
  • Стабильность весов в портфеле 
  • Приемлемые спреды 
  • Время возврата к среднему 
  • Ликвидность 
  • Транзакционные издержки 

Помимо этих критериев можно добавить ещё два, не упомянутых в том перечне:

  • Таймфрейм (частота данных)
  • Окно ретроспективного анализа

Вопрос заключается в том, как определить значения и выбрать вес каждого критерия? Похоже, что в итоге мы придем к какой-то «системе взвешенного начисления баллов» по каждому критерию, но как их взвесить? Как определить, какой из них будет более актуальным, если таковой вообще найдется? Как определить приоритетность этих критериев? Что идет первым, а что — в конце процесса ранжирования? Благодаря четкому определению того, как следует оценивать эти критерии, мы устраняем разрыв между теоретическим описанием модели и проверяемой, практичной и потенциально пригодной для использования торговой системой.

Мы предлагаем систему подсчета баллов, включающую два основных типа критериев: отборочные и классификационные.

Критерии исключения — это критерии, при нарушении которых корзина считается недействительной. Неважно, насколько хорошо корзина оценивается по другим факторам; если она не достигает установленного порога по этим критериям, её следует отправить в конец очереди. Эти критерии являются решающими факторами.

С другой стороны, классификационные критерии и составляют саму систему ранжирования.

Помимо этих двух основных критериев — отборочного и классификационного — существуют ещё два критерия, которые могут выступать в качестве отборочных или классификационных в зависимости от наших торговых целей. Это стратегические критерии.



Система подсчета очков

Отсеивающие критерии

Установив критерии отбора, мы гарантируем, что будем оценивать только те корзины, которые действительно можно торговать. Даже идеальная корзина, с точки зрения статистики, не представляет никакой ценности, если ею нельзя эффективно торговать. Нам следует рассмотреть возможность присвоения им высокого отрицательного веса.

Но почему бы не исключить их с помощью фильтра, который мы рассматривали в предыдущей статье? Потому что ценности, лежащие в основе этих критериев, не являются чем-то неизменным. Они могут измениться со временем и, скорее всего, изменятся. Именно поэтому у нас есть фильтр первичного отбора. Именно тогда мы выбираем те ценные бумаги, которые соответствуют нашей общей стратегии (рынки, секторы, отрасли и т. д.). Затем следует второй этап, на котором мы пропускаем эти ценные бумаги через наш фильтр оценки. В то время как на этапе отбора будет произведен отбор из тысяч потенциальных кандидатов, на этапе оценки будет определено, кто из относительно небольшого числа кандидатов является лучшим. По определению, система оценки должна указывать, какая пара или корзина в данный момент является наилучшим кандидатом для торговли.

Ликвидность — ликвидность является незаменимым критерием, поскольку торговля с использованием спредов, включающих неликвидные акции, может обходиться дорого. Нам нужны приемлемые спреды, и не только для статистического арбитража. Объем торгов за определенный период является наиболее наглядным показателем ликвидности ценной бумаги. Чем больше объем, тем выше ликвидность, что напрямую связано со спредами. 

Из документации по MetaTrader 5 мы узнаем , что понятие «объемы торгов» имеет разное значение для децентрализованных рынков, таких как Forex, и централизованных, таких как фондовый рынок:

«На рынке Форекс под «объемом» понимается количество тиков (изменений цены), произошедших за определенный промежуток времени. «В случае биржевых ценных бумаг под объемом понимается объем заключенных сделок (в контрактах или денежном выражении)».

Объем торгов и соответствующие спреды — это не единственные факторы, которые следует учитывать при оценке ликвидности ценной бумаги. Следует также учитывать глубину рынка, если бы мы торговали исключительно большими объёмами, что в данном случае не имеет места. Поэтому мы не будем использовать DOM в нашей системе статистического арбитража для розничных трейдеров.

Транзакционные издержки — Существует как минимум три фактора, которые могут повлиять на общие транзакционные издержки в зависимости от биржи, брокера и активов, включенных в нашу корзину: 

  • комиссионные и/или сборы 
  • спред
  • и ожидаемое проскальзывание.

На момент написания статьи спред по ликвидным акциям Nasdaq, таким как Apple (AAPL), Microsoft (MSFT) и Nvidia (NVDA), обычно составляет всего 0,01 доллара за акцию, однако для менее ликвидных акций он может быть больше. В этой системе оценки мы можем принять это значение для наиболее ликвидных инструментов, а для менее ликвидных — увеличить его до максимального спреда в 0,05 доллара.

Если я не ошибаюсь, у большинства брокеров возможность задавать максимально допустимое проскальзывание (отклонение от последней котировки) доступна только для профессиональных счетов, поэтому она выходит за рамки нашей системы статистического арбитража, предназначенной для розничных (непрофессиональных) трейдеров. В связи с этим ограничением мы должны учитывать в наших бэктестах некоторое ожидаемое проскальзывание. Можно принять ожидаемое проскальзывание на уровне 0,05 % от стоимости сделки на один ордер.

Наконец, что касается комиссий и/или сборов, следует учитывать, что, хотя многие брокеры предлагают торговлю без комиссии, все же могут взиматься регуляторные сборы и биржевые комиссии. В наших бэктестах мы можем использовать оценку в размере 0,005 доллара за акцию.

Чтобы упростить наши бэктесты, мы можем принять приблизительную величину совокупных затрат на полную сделку (покупка + продажа) равной 0,1 % от суммы сделки. Данное упрощение должно учитывать спреды, возможный проскальзывание, а также комиссии и сборы в целом. Но давайте сразу оговоримся: это лишь приблизительные оценки, основанные на моем личном опыте, и ваши результаты могут значительно отличаться от этих цифр. Поэтому, как и всегда, вам следует проявить должную осмотрительность при рассмотрении этого вопроса.



Ранжирующие критерии

Ранжирующие критерии — это те критерии, которые позволяют правильно ранжировать кандидатов в нашу корзину.

Степень коинтеграции — для оценки степени коинтеграции рядов можно использовать статистические показатели Йохансена (трас и максимальное собственное значение). Более сильные тестовые статистики свидетельствуют о более надежной долгосрочной зависимости.

Число коинтегрирующих векторов (ранг) — Учитывая число коинтегрирующих векторов, мы можем оценить желаемую сложность создаваемой системы. 

  • Ранг = 1 означает один стабильный спред. Проще контролировать.
  • Ранг >1 означает наличие нескольких возможных диапазонов. Более гибкий, но и более сложный.

Стабильность весов в портфеле — мы можем избежать значительных колебаний коэффициентов хеджирования в разных временных интервалах выборки. Это может свидетельствовать о нестабильности отношений.

Время возврата к равновесию — мы можем рассчитать период полураспада возврата к среднему значению, чтобы оценить, как быстро спред возвращается к равновесию.



Стратегические критерии

Частота данных (временной интервал) и окно ретроспективного анализа в основном являются стратегическими решениями, а не количественными критериями для оценки. Дело не столько в том, что один из них «лучше» другого, сколько в том, насколько он соответствует нашей торговой стратегии.  В стратегии с долгосрочной перспективой могут использоваться ежедневные данные, тогда как в стратегии, ориентированной на краткосрочные возможности, — внутридневные данные. Таким образом, мы не обязательно придаем этому критерию такое же значение, как остальным. Вместо этого мы будем использовать его в качестве фильтра предварительного отбора. Сначала мы определим, является ли наша стратегия долгосрочной или краткосрочной, а затем подберем корзины, используя данные с соответствующей периодичностью. Частота обновления данных может влиять на оперативность сигнала, уровень шума и транзакционные издержки, поэтому это основополагающее решение, которое определяет, как мы будем оценивать остальные критерии.

Частота данных (ежедневная, внутридневная) — Ежедневные данные обычно используются в стратегиях с длительным горизонтом, тогда как внутридневные данные позволяют использовать краткосрочные возможности. Более высокая частота повышает оперативность передачи сигналов, но приводит к увеличению шума и транзакционных издержек.

Окно ретроспективного анализа для оценки стабильности — выбор длительности исторического периода для тестов на корреляцию и коинтеграцию имеет решающее значение. Короткие временные интервалы позволяют быстро адаптироваться к меняющимся рыночным условиям, но могут отражать кратковременные и нестабильные взаимосвязи. Длительные временные интервалы обеспечивают более стабильные оценки, но могут утратить актуальность в случае изменения экономической ситуации. 

Практичный подход заключается в том, чтобы протестировать несколько значений длины окна (например, 60, 120, 250 торговых дней) и оценить, насколько стабильными остаются результаты коинтеграции при разных значениях длины окна. 

Мы также можем использовать скользящие окна для постоянного обновления коэффициентов хеджирования и проверки того, сохраняется ли эта зависимость вне выборки.

Не следует путать этот стратегический выбор периода ретроспективного анализа с оценкой стабильности вышеупомянутых окон ретроспективного анализа. Здесь мы НЕ будем оценивать стабильность для разных окон ретроспективного анализа; вместо этого мы выберем, какое окно обратного анализа использовать в качестве основы для расчета среднего значения, стандартного отклонения и частоты обновления параметров советником. В частности, следует отметить, что между торговым таймфреймом и периодом обратного анализа нет никакой внутренней связи. С точки зрения трейдинга вполне допустимо торговать на дневном таймфрейме и использовать внутридневные периоды обратного просмотра. На самом деле, в этом сочетании кроются некоторые неожиданные возможности, как мы вскоре увидим.



Настроить процесс оценки

Ликвидность

С учетом этих критериев мы готовы приступить к оценке отобранных корзин. Для начала мы можем отсеять те акции, которые не соответствуют нашей стратегии в целом, а именно акции с низкой ликвидностью. Это критерий исключения. Однако, если мы не можем заранее заявить, что не будем включать акции, объем торговли которыми за определенный период составляет менее X единиц, ликвидность остается относительным показателем. Нам нужен ориентир для оценки высокой, средней и низкой ликвидности; необходимо определить пороговое значение, позволяющее отличить приемлемые показатели от неприемлемых за данный период.

Ликвидность и, как следствие, приемлемые спреды не должны представлять собой проблему в конкретном случае, который мы приводим в качестве примера с самого начала этой серии, а именно в отношении акций Nasdaq. Как правило, это акции с относительно высокой ликвидностью. Однако даже среди этих акций с относительно высокой ликвидностью некоторые из них отличаются большей ликвидностью, чем другие. Кроме того, следует учитывать, что в какой-то момент нам, по тем или иным причинам, может потребоваться проводить анализ или оценку акций компаний, не входящих в индекс Nasdaq, или даже заниматься исключительно акциями компаний с малой капитализацией. В таком случае ликвидность, безусловно, станет одним из факторов, которые необходимо учитывать.

Давайте продолжим рассматривать акции компаний из полупроводниковой отрасли, которые мы уже отфильтровали. На момент написания статьи их количество составляет шестьдесят три символа, хранящихся в нашей базе данных. (Если вы не следите за этой серией статей, прочитайте хотя бы предыдущую статью, чтобы понять, как мы пришли к этому выводу после отбора. )

Рис. 1 — Снимок экрана, демонстрирующий интерфейс базы данных в Metaeditor с указанием количества символьных таблиц

Рис. 1 — Снимок экрана, демонстрирующий интерфейс базы данных в Metaeditor с показателем количества записей в таблице market_data

Как уже было сказано, ожидается, что все акции, котирующиеся на Nasdaq, будут ценными бумагами с относительно высокой ликвидностью. Именно по этой причине мы выбираем их при разработке функционала нашей платформы для статистического арбитража, предназначенной для обычного розничного трейдера. Но нас интересует, какие из компаний, входящих в подсектор полупроводников, являются наиболее торгуемыми, а также какие — наименее торгуемыми.

Рис. 2 — Снимок экрана, демонстрирующий интерфейс базы данных в Metaeditor с наиболее ликвидными акциями компаний-производителей полупроводников

Рис. 2 — Снимок экрана, демонстрирующий интерфейс базы данных в Metaeditor с акциями наиболее ликвидных производителей полупроводников

Здесь нет ничего удивительного. Как и ожидалось, на момент написания этой статьи акции Nvidia являются самыми торгуемыми и наиболее ликвидными в полупроводниковой отрасли, а также одними из самых торгуемых во всем мире. Но эта информация не представляет особой ценности. Нам нужно вычислить среднее значение для каждого символа, чтобы мы могли сравнить их между собой.

Рис. 3 — Снимок экрана, демонстрирующий интерфейс базы данных в Metaeditor со средним объемом торгов акциями компаний-производителей полупроводников

Рис. 3 — Снимок экрана, демонстрирующий интерфейс базы данных в Metaeditor со средним объемом торгов акциями компаний-производителей полупроводников

Помните, что в случае с рынком Форекс здесь используется тиковый объем, а не реальный объем.

Вот это уже что-то полезное. Если вы не знакомы с SQL, возможно, вам стоит запомнить этот простой запрос, поскольку в будущем он вам неоднократно пригодится в небольших вариациях.

SELECT
    s.symbol_id,
    s.ticker,                                 -- symbol name
    ROUND(AVG(md.real_volume)) AS avg_real_volume_int
FROM market_data AS md
JOIN symbol AS s
      ON md.symbol_id = s.symbol_id
GROUP BY s.symbol_id, s.ticker
ORDER BY avg_real_volume_int DESC;

ROUND и AVG — это две встроенные функции SQLite. Как видно из их названий, мы используем AVG для расчета среднего реального объема. Оно возвращается в виде числа с плавающей запятой. Функция ROUND округляет результат до ближайшего целого числа.

Оператор JOIN используется для сопоставления каждой строки рыночных данных с метаданными по символу, что позволяет получить название символа на основе его идентификатора. Наконец, мы используем оператор GROUP BY, чтобы в результатах отображалась только одна строка на каждый символ.



Сила коинтеграции

Теперь, когда мы получили список акций компаний полупроводниковой отрасли, отсортированный по ликвидности, пришло время проверить степень их коинтеграции. Как и в случае с предыдущими тестами (корреляция, коинтеграция и стационарность), результаты данного ранжирования по силе коинтеграции также будут сохранены в отдельной таблице для дальнейшего использования и сравнения. Помните, что мы сохраняем все данные, которые могут пригодиться в будущем при внедрении технологий машинного обучения.

Новая таблица coint_rank имеет следующий вид.

Рис. 4 — Снимок экрана, демонстрирующий поля и типы данных таблицы coint_rank

Рис. 4 — Снимок экрана, демонстрирующий поля и типы данных таблицы coint_rank

Определение таблицы очень похоже на то, что мы делали до сих пор. Мы по-прежнему используем временную метку теста в качестве первичного ключа, поскольку временные метки являются своего рода естественным ключом в нашей области. Мы продолжаем использовать таблицы STRICT в SQLite, чтобы избежать неожиданностей, связанных с неверными типами данных, и проводим стандартные проверки в полях TEXT, когда допустимые значения известны заранее, например, в случае метода коинтеграции и временных интервалов. Приведенное ниже определение содержит комментарии, облегчающие понимание.

-- Cointegration ranking results (STRICT mode with lookback and timestamp uniqueness)

CREATE TABLE coint_rank (
    tstamp INTEGER PRIMARY KEY,        -- Unix timestamp (UTC seconds since epoch)
    timeframe TEXT CHECK (
        timeframe IN (
            'M1','M2','M3','M4','M5','M6','M10','M12','M15','M20','M30',
            'H1','H2','H3','H4','H6','H8','H12','D1','W1','MN1'
        )
    ) NOT NULL,

    lookback INTEGER NOT NULL,         -- Lookback period (calendar days)
    assets TEXT NOT NULL,              -- e.g., 'AAPL, MSFT' or 'AAPL, MSFT, GOOG'
    method TEXT CHECK (
        method IN ('Engle-Granger', 'Johansen')
    ) NOT NULL,

    strength_stat REAL,                -- Engle-Granger: ADF statistic; Johansen: trace statistic
    p_value REAL,                      -- Only for Engle-Granger
    eigen_strength REAL,               -- Johansen eigenvalue indicator
    rank_score REAL,                   -- Combined ranking score

    -- Allow multiple test runs for the same combo but unique by timestamp
    CONSTRAINT coint_rank_unique_combo UNIQUE (timeframe, lookback, assets, tstamp)
) STRICT;

CREATE INDEX idx_coint_rank_timeframe_lookback
    ON coint_rank (timeframe, lookback);

Мы используем ограничение UNIQUE для комбинаций «время/период/история данных/активы», поэтому знаем, что для каждой из этих пар у нас будет только одна оценка ранга коинтеграции на каждом временном отрезке. Обратите внимание, что, хотя tstamp по определению уже является уникальным, поскольку является первичным ключом таблицы, если мы не включим его в набор UNIQUE, мы не сможем вставить более одной записи для каждой комбинации «период времени — окно ретроспективного анализа — активы». Это привело бы к нарушению ограничения UNIQUE. Включив временную метку в комбинацию, мы можем сохранить одну и ту же комбинацию с разными временными метками, чтобы проверить стабильность коинтеграции во времени. Позже мы сможем отфильтровать или сгруппировать результаты по дате. Это будет полезно для наших журналов бэктестинга, поскольку каждый прогон представляет собой самостоятельную единицу, снабженную отметкой времени и позволяющую отследить его ход. Наконец, поскольку мы в основном будем выполнять запросы по временному интервалу и периоду обратного просмотра, мы создали индекс для этих двух столбцов, чтобы ускорить работу.

Коэффициент мощности для критерия Энгл-Грейнджера

В предыдущих частях этой серии мы уже подробно рассказывали о том, как работают тесты коинтеграции Энгл-Грейнджера и Йохансена, поэтому, чтобы сэкономить ваше время, мы не будем повторять эту информацию здесь. Давайте просто помнить, что они показывают, насколько синхронно движутся в долгосрочной перспективе пара или группа акций. То есть они оценивают, насколько устойчива ценовая взаимосвязь между этими активами с течением времени. В поле strength_stat хранится численный результат теста на коинтеграцию как для модели Энгл-Грейнджера, так и для модели Йохансена.

Для теста Энгл-Грейнджера в этом поле хранится статистика ADF (расширенный тест Дики-Фуллера), примененная к разнице между двумя активами. Как уже упоминалось ранее, мы уже рассказывали об особенностях и интерпретации теста стационарности ADF в предыдущих статьях этой серии, поэтому не будем повторяться. Достаточно помнить, что он проверяет, колеблется ли спред между этими двумя активами вокруг среднего значения, а не отклоняется от него. То есть проверяется, является ли дисперсия стационарной. Таким образом, в случае теста коинтеграции Энгл-Грейнджера это поле показывает нам, «насколько» стационарен спред.

Если вы следили за нашей аргументацией в предыдущих частях, то к этому моменту должно быть ясно: чем меньше значение статистики ADF, тем сильнее коинтеграция; «более отрицательное» значение поля strength_stat в тесте Энгл-Грейнджера означает, что спред является «более» стабильным.

Показатель силы в тесте Йохансена

При тестировании не пары активов, а корзины из трёх и более активов мы используем тест коинтеграции Йохансена для построения наших взвешенных портфелей, то есть линейных комбинаций, которые остаются стабильными во времени. Мы уже описывали этот тест и то, как интерпретировать его результаты, в предыдущих статьях. И снова мы поможем вам сэкономить время, предложив ознакомиться с этими статьями. В данном тесте поле strength_stat в нашей базе данных хранит «статистику трассировки». Чем выше trace statistic, тем убедительнее свидетельство того, что некоторая комбинация этих активов образует стабильное долгосрочное равновесие. Чем больше значение параметра strength_stat, тем убедительнее свидетельство того, что эта корзина движется синхронно в состоянии равновесия.

В приложенном скрипте на Python реализована следующая логика, определяющая, когда использовать тот или иной тест и как получать их результаты.

try:
    if len(basket) == 2:
        y0, y1 = sub_prices.iloc[:, 0], sub_prices.iloc[:, 1]
        score, p_value, _ = coint(y0, y1)
        results.append({
            "assets": basket,
            "method": "Engle-Granger",
            "strength_stat": float(score),
            "p_value": float(p_value),
            "eigen_strength": None
        })
    else:
        johansen_res = coint_johansen(sub_prices, det_order, k_ar_diff)
        max_eig = float(max(johansen_res.eig))
        results.append({
            "assets": basket,
            "method": "Johansen",
            "strength_stat": float(max(johansen_res.lr1)),
            "p_value": None,
            "eigen_strength": max_eig
        })

Ниже представлено наглядное изображение этой логики.

Рис. 5 — Блок-схема, иллюстрирующая процесс заполнения поля strength_stat, и его упрощённая интерпретация

Рис. 5 — Блок-схема, иллюстрирующая процесс заполнения поля strength_stat, и его упрощённая интерпретация

Сила по собственному значению

В то время как тест Энгл-Грейнджера предоставляет только показатель ADF, который мы сохраняем в поле strength_stat, тест Йохансена также содержит ещё один показатель, помогающий в оценке: собственную силу, которую мы сохраняем в поле eigen_strength. Для корзин из трёх и более активов тест Йохансена выявляет линейные комбинации (которые станут весами нашего портфеля) этих активов, между которыми существуют стабильные долгосрочные взаимосвязи. Каждая комбинация соответствует вектору коинтеграции, и каждому из них присвоено собственное значение. Собственное значение показывает, насколько «сильной» является данная коинтеграционная зависимость и насколько тесно активы движутся вместе в этом равновесном направлении. Более высокое собственное значение означает, что активы находятся в более стабильной долгосрочной взаимосвязи, а также что небольшие отклонения от равновесия, как правило, быстрее нивелируются.

В приведенном ниже скрипте мы выбираем максимальное собственное значение из всех обнаруженных векторов:

else:
   johansen_res = coint_johansen(sub_prices, det_order, k_ar_diff)
   max_eig = float(max(johansen_res.eig))
   results.append({
       "assets": basket,
       "method": "Johansen",
       "strength_stat": float(max(johansen_res.lr1)),
       "p_value": None,
       "eigen_strength": max_eig
  })

Статистика следа (trace statistic) Йохансена, хранящаяся в поле strength_stat, показывает, насколько плотно группа движется как единое целое, то есть насколько стабильны взаимосвязи. Максимальное собственное значение (eigen_strength) показывает, насколько прочна самая стабильная связь между ними. Таким образом, высокое значение eigen_strength свидетельствует о том, что группа движется в очень плотном строю, тогда как низкое значение eigen_strength указывает на то, что ее члены легче расходятся, даже если они коинтегрированы. 

Итоговый оценочный балл

Поле rank_score представляет собой итоговый сводный показатель, который генерирует наш скрипт оценки; именно он определяет, какие корзины выглядят наиболее перспективными и заслуживают внимания при торговле. После проведения тестов на коинтеграцию нам потребуется единый сопоставимый показатель для всех результатов, независимо от того, получены ли они на основе пар Энгл-Грейнджера или корзин Йохансена. Это число и есть rank_score. Мы можем рассматривать показатель rank_score как нормализованный индекс силы коинтеграции, который позволяет нам сортировать и ранжировать все наши потенциальные взаимосвязи от самых сильных к самым слабым.

Вы найдёте это в этом фрагменте нашего прилагаемого скрипта оценки:

df = pd.DataFrame(results)

df["rank_score"] = df.apply(lambda row: -row["p_value"] if row["method"] == "Engle-Granger" else row["eigen_strength"], axis=1)

df = df.sort_values("rank_score", ascending=False).reset_index(drop=True)

Для теста Энгл–Грейнджера (парного) вычисляется p-значение. Чем меньше значение p, тем убедительнее доказательства коинтеграции. Чтобы более сильные пары занимали более высокие места в итоговом рейтинге, мы берем обратное значение p-значения:

rank_score = −p_value

Таким образом, p-значение 0,01 превращается в -0,01, которое при сортировке оказывается выше более слабого значения, такого как -0,20. Чем ниже значение p, тем выше (менее отрицательным) является показатель rank_score, а значит, эта пара считается более коинтегрированной.

В случае теста Йохансена (Baskets) получается не одно p-значение, а несколько собственных значений. Наш скрипт берет наибольшее собственное значение, сохраненное в переменной `eigen_strength`, и использует его напрямую в качестве значения `rank_score`.

rank_score = eigen_strength

Более высокое собственное значение означает более высокий показатель rank_score и более сильную корзину.

Для упрощения и удобства анализа в rank_score все пары и корзины представлены на одной странице. Ему неважно, из какого теста они взяты, важно только, насколько сильна эта зависимость. Мы можем использовать SQL для фильтрации результатов по своему усмотрению (в конце концов, ведь именно в этом и заключается главная причина использования базы данных, не так ли?). Когда мы сортируем по rank_score (по убыванию), в верхней части списка оказываются наиболее стабильные и статистически значимые ценовые взаимосвязи.

Рис. 6 — Снимок экрана интерфейса базы данных в Metaeditor, на котором показана таблица coint_rank с данными по предприятиям полупроводниковой отрасли

Рис. 6 — Снимок экрана интерфейса базы данных в Metaeditor, на котором показана таблица coint_rank с данными по предприятиям полупроводниковой отрасли

Здесь мы отобрали десять наиболее ликвидных активов, представленных на рисунке 3 выше, и запустили прилагаемый скрипт для ранжирования коинтеграции по всем комбинациям из двух, трёх и четырёх инструментов, причём все расчёты проводились для таймфрейма H4. Этот срок является одним из наших стратегических критериев. На рисунке 6 выше показан первый прогон, который мы провели для 60-дневного периода. Синим цветом в Metaeditor выделен уровень, на котором показатели ранжирования переходят от значений по методу Йохансена к значениям по методу Энгл-Грейнджера. Набор результатов заполнен, фильтры пока не применены.

Вы заметите, что для этого одного прогона получено 375 результатов (до прогона эта таблица была пуста). Это количество возможных комбинаций (не перестановок) из 10 символов, сгруппированных по 2, 3 или 4. Помните об этом числе при проведении собственных тестов, поскольку вам необходимо учитывать «комбинаторный взрыв», с которым вам, возможно, придётся столкнуться. Именно по этой причине я решил протестировать здесь только один временной интервал, помимо того, что он является нашим «стратегическим критерием» для демонстрации. Кроме того, именно по этой причине я ограничил количество символов в каждой корзине четырьмя. Позже нам понадобится провести тестирование для нескольких периодов анализа, поскольку без сравнения результатов за несколько таких периодов этот рейтинг будет малополезен или, по крайней мере, весьма ненадежен. Если мы добавим больше символов и временных интервалов, нам потребуется гораздо больше ресурсов — как вычислительной мощности, так и времени, а также данных. Помните об этом и подстраивайте цифры под свои ресурсы и цели, а не наоборот.

Здесь представлена верхняя часть той же таблицы.

Рис. 7 — Снимок экрана интерфейса базы данных в Metaeditor, на котором показана верхняя часть таблицы coint_rank

Рис. 7 — Снимок экрана интерфейса базы данных в Metaeditor, на котором показана верхняя часть таблицы coint_rank

Обратите внимание, что в верхней части таблицы преобладают результаты Йохансена. Это неудивительно, поскольку данный метод, как правило, выявляет более сильные взаимосвязи, чем метод Энгл-Гранжера. Но также следует учитывать, что мы имеем дело с очень небольшой выборкой, включающей лишь один таймфрейм (H4) и один период обратного анализа (60 дней). 

Однако даже на основе этой ограниченной выборки начинают вырисовываться определенные закономерности, подобно той, что выделена тремя линиями на рисунке 7. У нас есть шесть символов, объединенных в три группы по четыре символа, но изменение одного из них практически не влияет на результат. Это может свидетельствовать о том, что остальные три символа, которые не изменяются в корзине (NVDA, MU и ASX), являются «истинным источником» коинтеграции. Далее мы видим, что эта тройка формирует собственную корзину на пять строк ниже, поэтому у них коинтеграция слабее, чем в том случае, когда остальные компоненты входили в эту корзину. Почему? Разве не предполагалось, что они станут прочной основой коинтеграции? Ну, я пока не знаю ответа. Но я уверен, что по мере нашего продвижения вперед, анализируя эти десять символов, мы обнаружим множество подобных закономерностей, а также и другие, ещё более «неожиданные». И многие из них будут торговыми моделями, которые мы не смогли бы обнаружить без помощи анализа данных. В этом и заключается прелесть статистического арбитража и, в данном случае, прелесть торговли на основе данных в целом.

У нас будет достаточно времени, чтобы найти эти закономерности в наших данных — как вручную, так и с помощью визуализации результатов на графиках. А пока давайте сосредоточимся на нашей первоначальной цели — автоматизации этого рабочего процесса для заполнения таблицы стратегий, которую мы создали в качестве «единого источника достоверной информации» для нашего советника.

Как только наша таблица coint_rank будет заполнена данными по десяти наиболее ликвидным инструментам для таймфрейма H4 и периодов анализа от одного до шести месяцев, при сортировке по rank_score в порядке убывания в верхней части списка результатов окажется корзина с наибольшей коинтеграцией. 

Помните, что наша исходная торговая гипотеза заключается в поиске акций, коинтегрированных с NVDA. Итак, давайте проверим.

Рис. 8 — Снимок экрана интерфейса базы данных в Metaeditor, на котором показана таблица coint_rank

Рис. 8 — Снимок экрана интерфейса базы данных в Metaeditor, на котором показана таблица coint_rank

Здесь мы используем оператор LIKE из SQLite, чтобы отфильтровать только корзины и пары, в которых NVDA встречается в любом месте.

«Оператор LIKE выполняет сравнение по шаблону. Операнд справа от оператора LIKE содержит шаблон, а операнд слева — строку, которая будет сравниваться с этим шаблоном. Символ процента («%») в шаблоне LIKE соответствует любой последовательности из нуля или более символов в строке. (Более подробную информацию можно найти в документациипо SQLite.)

Если мы хотим торговать парами, а не корзинами, мы проводим фильтрацию с помощью метода проверки коинтеграции, используя исключительно метод Энгл-Грейнджера. (Мы могли бы также отфильтровать приведенный выше запрос, запросив только результаты теста Йохансена, но в этом не было необходимости, поскольку, как уже было сказано, результаты Йохансена превосходят результаты Энгл-Грангера).

Рис. 9 — Снимок экрана интерфейса базы данных в Metaeditor, на котором показана таблица coint_rank с фильтром по тесту Энгл-Грейнджера

Рис. 9 — Снимок экрана интерфейса базы данных в Metaeditor, на котором показана таблица coint_rank с фильтром по тесту Энгл-Грейнджера

Мы видим, что у нас есть как минимум один сильный кандидат для формирования корзины на основе 60-дневного периода, а также два подходящих кандидата для парной торговли: один — на основе 180-дневного периода, а другой — на основе 60-дневного периода. Давайте останемся при корзине, так как именно этим мы и занимались, и воспользуемся парами для ваших экспериментов (в первой статье этой серии вы найдете советник для торговли парами, с помощью которого можно тестировать их по своему усмотрению).

Чтобы провести бэктестинг более сильной корзины, нам понадобятся веса портфеля. Мы будем использовать скрипт, представленный в предыдущей статье, — coint_johansen_to_db.py — для получения весов портфеля и одновременно сохраним результаты тестирования в файле coint_johansen_test для последующего анализа.

if __name__ == '__main__':    

    analyzer = SymbolJohansenMulti()

    # Our best-ranked NVDA cointegrated basket
    analyzer.run_johansen_analysis(
        asset_tickers=['NVDA',  'INTC', 'AVGO', 'ASX'],
        timeframe='H4',
        lookback=60
    )

Если всё пройдёт успешно, в терминале должен появиться следующий вывод.

PS C:\...\StatArb\coint> python .\py\screening\coint_johansen_to_db.py

Подключение к базе данных SQLite установлено.

Загрузка данных для символа: NVDA...

Загрузка данных для символа: INTC...

Загрузка данных для символа: AVGO...

Загрузка данных для символа: ASX...

Результаты теста Йохансена для 4 активов:

  Ранг коинтеграции: 1

  Trace-статистика (показывает, насколько устойчивы совместные долгосрочные связи внутри группы): [56.636209656616835, 14.38576476690755, 5.106951610164332, 1.355015009275225]

  Статистика максимального собственного значения: [42.250444889709286, 9.278813156743219, 3.7519366008891066, 1.355015009275225]

  Коинтегрирующие векторы: [1.0, -1.9598590335874817, -0.36649674991957104, 21.608207065113874]

Успешно сохранены результаты тестирования Йохансена для 4 объектов с идентификаторами test_id: 21.

Соединение с базой данных прервано.

Анализ завершен.

Последний шаг — внести эти значения в нашу таблицу «strategy», чтобы советник мог их прочитать, и провести бэктест. 



Ретроспективный анализ

Хотя встроенная в MetaTrader 5 база данных SQLite полностью доступна во время торговли, в Тестере стратегий мы не можем считывать данные из баз данных SQLite. Полагаю, причина в том, что Strategy Tester использует отдельную папку агента (что подразумевает работу в изолированной среде). Во время тестирования все операции с файлами выполняются в локальной папке тестового агента, а не в стандартной папке терминала MQL5\Files. Мы могли бы экспортировать таблицу «strategy» в файл CSV и загрузить её в начале бэктеста в обработчике событий OnInit() советника. Это позволило бы нам считывать данные из таблицы «strategy», но мы по-прежнему не смогли бы обновлять модель в режиме реального времени во время тестирования на исторических данных, а ведь именно в этом и заключается основная цель использования базы данных в качестве источника данных для модели. Но не паникуйте! Мы можем проверить, как обновляется модель в режиме реального времени, на демо-счете. А пока давайте просто введем параметры модели напрямую в советник и проведем бэктестинг каждой стратегии по отдельности.

Поскольку мы используем MQL5 Algo Forge — новую систему хранения MQL5 на базе Git — мы можем легко проследить изменения, внесенные в наш советник для добавления этих параметров.

// Input parameters
 input int    InpUpdateFreq  = 1;     // Update frequency in minutes
-input string InpDbFilename = "StatArb\\statarb-0.3.db"; // SQLite database filename
-input string InpStrategyName = "CointNasdaq"; // Strategy name
+input string InpDbFilename = "StatArb\\statarb-0.4.db"; // SQLite database filename
+input string InpStrategyName = "CointNasdaq_H4_60"; // Strategy name
 input double InpEntryThreshold = 2.0;    // Entry threshold (std dev)
 input double InpExitThreshold = 0.3;     // Exit threshold (std dev)
 input double InpLotSize = 10.0;           // Lot size per leg
@@ -53,18 +53,40 @@ int OnInit()
 // Set a timer for spread, mean, stdev calculations
 // and strategy parameters update (check DB)
    EventSetTimer(InpUpdateFreq * 60); // min one minute
-// Load strategy parameters from database
-   if(!LoadStrategyFromDB(InpDbFilename,
-                          InpStrategyName,
-                          symbols,
-                          weights,
-                          timeframe,
-                          lookback_period))
+// check if we are backtesting
+   if(MQLInfoInteger(MQL_TESTER))
      {
-      // Handle error - maybe use default values
-      printf("Error at " + __FUNCTION__ + " %s ",
-             getUninitReasonText(GetLastError()));
-      return INIT_FAILED;
+      Print("Running on tester");
+      ArrayResize(symbols, 4);
+      ArrayResize(weights, 4);
+      //{"NVDA", "INTC", "AVGO", "ASX"};
+      symbols[0] = "NVDA";
+      symbols[1] = "INTC";
+      symbols[2] = "AVGO";
+      symbols[3] = "ASX";
+      // {1.0, -1.9598590335874817, -0.36649674991957104, 21.608207065113874};
+      weights[0] = 1.0;
+      weights[1] = -1.9598590335874817;
+      weights[2] = -0.36649674991957104;
+      weights[3] = 21.608207065113874;
+      timeframe = PERIOD_H4;
+      lookback_period = 60;
+     }
+   else
+     {
+      // Load strategy parameters from database
+      if(!LoadStrategyFromDB(InpDbFilename,
+                             InpStrategyName,
+                             symbols,
+                             weights,
+                             timeframe,
+                             lookback_period))

Как видите, мы обновили имя файла базы данных, чтобы отразить изменения в схеме (мы добавили таблицу coint_rank), а также название стратегии, чтобы указать, какой тест коинтеграции лежал в основе выбора её параметров, что сделало название более конкретным. 

Мы также добавили проверку, позволяющую определить, работает ли программа в среде Strategy Tester, то есть проводится ли бэктест. В таком случае мы не будем извлекать параметры из базы данных. Вместо этого мы будем использовать жестко заданные параметры стратегии.

Вот настройки бэктеста и используемые в нём входные данные. Внизу этой статьи вы найдете как файлы настроек для бэктеста (.ini), так и файлы входных данных для бэктеста (.set).

Рис. 10 — Снимок экрана с настройками бэктеста

Рис. 10 — Снимок экрана с настройками бэктеста

Обратите внимание, что мы провели бэктест за период в два с половиной года, начиная с начала 2023 года, несмотря на то, что период обратного анализа нашей стратегии составляет 60 дней. Кроме того, мы оставили поле «Задержки» настроенным на идеальное выполнение (нулевая задержка), даже зная, что это может стать критическим фактором для стратегий статистического арбитража.

Оба этих решения обусловлены тем, что основная цель этого первого бэктеста заключается в оценке долгосрочной стабильности стратегии, а не её потенциальной прибыльности на данном этапе. Так почему бы с самого начала не установить период обратного обзора для теста коинтеграции равным двум с половиной годам? Поскольку в данный момент мы фиксируем наибольшую коинтеграцию, сейчас мы рассматриваем возможность совершения сделки. Долгосрочная стабильность — это ещё один фактор, который необходимо учитывать в нашей системе оценки, однако при составлении рейтинга корзин нас интересуют те из них, которые на сегодняшний день демонстрируют наибольшую коинтеграцию.

Что касается нулевой задержки, то в данном случае это не должно иметь никакого значения. Помните, как мы решили торговать на более высоких таймфреймах, чтобы избежать неравной конкуренции с крупными игроками , занимающимися статистическим арбитражем? Мы торговали на минутном таймфрейме. Сейчас мы находимся на таймфрейме H4, в более спокойных условиях для свинг-трейдинга.

Рис. 11 — Снимок экрана с параметрами для бэктеста

Рис. 11 — Снимок экрана с параметрами для бэктеста

На рисунке 11 показано, что мы провели ряд оптимизаций для выбора оптимальных пороговых значений входа и выхода, оба из которых рассчитываются как стандартные отклонения от среднего значения. В бэктесте использовались значения 6,9 и 0,1 соответственно.

Вот результаты бэктеста.

Рис. 12 — Отчет по бэктесту с результатами статистики

Рис. 12 — Отчет по бэктесту с результатами статистики

Советник открыл 160 сделок, что в среднем составляет 4,7 сделки в месяц за 34 месяца. Допустим, в среднем одна сделка в неделю — это должно быть оптимальным сроком удержания позиции для свинг-трейдинга. Кроме того, средняя прибыль по сделке (1 479,02)  превышает средний убыток по сделке (-1 369,62), при этом просадка баланса минимальна (1,27 %). Хотя прибыльность не была основной целью бэктеста, в целом результаты выглядят многообещающими. 

Рис. 13 — Результаты бэктеста, демонстрирующие график соотношения собственного капитала к балансу

Рис. 13 — Результаты бэктеста, демонстрирующие график изменения собственного капитала и остатка на счете

График динамики баланса/капитала выглядит немного лучше, чем я ожидал, поскольку система стала приносить прибыль примерно 24 ноября, а период ретроспективного анализа в нашем тесте на коинтеграцию был очень коротким (60 дней).

Рис. 14. Отчет по бэктесту с указанием дат и времени открытия торговых позиций

Рис. 14. Отчет по бэктесту с указанием дат и времени открытия торговых позиций

«Изучая данные Страуса, Лауфер обнаружил определенные повторяющиеся торговые последовательности, зависящие от дня недели. Например, динамика цен в понедельник часто повторяла динамику пятницы, тогда как во вторник наблюдалось возвращение к прежним тенденциям. Лауфер также обнаружил, что динамика торгов предыдущего дня часто позволяет предсказать активность на следующий день; это явление он назвал «24-часовым эффектом». «Модель «Медальон» предполагала, например, начать скупку в конце дня в пятницу при наличии явного восходящего тренда, а затем продавать в начале понедельника, используя то, что они называли «эффектом выходных»» (из книги Грегори Цукермана «Человек, который разгадал рынок»: Как Джим Симмонс положил начало квантитативной революции, Нью-Йорк, штат Нью-Йорк: Portfolio/Penguin, 2019)

Вы заметили закономерность в приведенном выше графике по часам? Все 160 сделок были открыты в одно и то же время, а именно в 16:30. 

Я полагаю, что в Стратегическом тестере речь идет о времени UTC.

«Во время тестирования локальное время TimeLocal() всегда равно серверному времени TimeTradeServer(). В свою очередь, время сервера всегда равно времени, соответствующему времени GMT — TimeGMT(). «Таким образом, все эти функции отображают одинаковое время во время тестирования» (Основы тестирования в MetaTrader 5)

Честно говоря, мне кажется довольно странным, что все сделки открываются в одно и то же время. Это может быть правдой, а может быть связано с неожиданным поведением какого-либо EA. В любом случае, это время закрытия Лондонской фондовой биржи, и именно такие закономерности мы и можем ожидать при анализе данных для статистического арбитража, как показывает приведенная выше цитата.

Рис. 15 — Отчет по бэктесту с показателями MFE и MAE

Рис. 15 — Отчет по бэктесту с показателями MFE и MAE

Как показатели MFE, так и MAE указывают на то, что для данной системы следует рассмотреть возможность введения порогового значения для закрытия позиций в зависимости от срока удержания, что подтверждается приведенными ниже данными о максимальном и среднем сроках удержания позиций. Чтобы узнать больше о MFE и MAE, вы можете ознакомиться с этой замечательной статьёй о математике в трейдинге.

Рис. 16 — Отчет по бэктесту с указанием сроков удержания позиций

Рис. 16 — Отчет по бэктесту с указанием сроков удержания позиций

Похоже, у нас была хотя бы одна вакансия, которая оставалась незаполненной около 15 недель! И даже средний срок удержания позиции, составляющий около месяца, кажется несколько завышенным для стратегии такого рода. 

Но мы рассмотрим эти вопросы позже. На данный момент достаточно понять принципы, лежащие в основе системы оценки, и адаптировать её к своему стилю и торговым целям. Как однажды сказал Джон фон Нейман: «Истина… слишком сложна, чтобы допускать что-либо, кроме приближений». 

Удачной торговли!



Заключение

В данной статье мы предлагаем систему оценки для статистического арбитража с использованием коинтегрированных акций. Сначала учитываются стратегические критерии, такие как временной интервал и окно обратного обзора для тестов на коинтеграцию, а затем применяются критерии исключения, такие как ликвидность и транзакционные издержки, чтобы отсеять ценные бумаги, торговля которыми не окупает затрат, или которые вообще не поддаются торговле.

В итоге рейтинг формируется с учетом таких факторов, как степень коинтеграции, количество векторов коинтеграции, стабильность весов портфеля и время возврата к среднему значению. В данной статье мы представили пример реализации первых двух классификационных критериев, оставив рассмотрение стабильности весов портфеля и периода возврата к среднему значению для следующей части.

Мы представили и прокомментировали результаты бэктеста, основанного на двух классификационных критериях, а также предоставили файлы, необходимые для воспроизведения тестов коинтеграции и самого бэктеста, чтобы читатели могли сразу же приступить к применению предложенной стратегии статистического арбитража и данной системы оценки.

Файл Описание
backtests\CointNasdaq.EURUSD.H4.20230101_20251019.000.ini Настройки конфигурации бэктеста
backtests\CointNasdaq.set Параметры бэктеста (файл SET)
Experts\StatArb\CointNasdaq.mq5 Исходный файл советника MQL5
Файлы\StatArb\schema-0.4.sql Схема базы данных (файл SQL)
Include\StatArb\CointNasdaq.mqh  Файл советника MQH (заголовок)
coint_ranker_auto.py Скрипт на Python для ранжирования результатов коинтеграционного анализа

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/20026

Прикрепленные файлы |
Создание самооптимизирующихся советников на MQL5 (Часть 16): Идентификация линейных систем на основе обучения с учителем Создание самооптимизирующихся советников на MQL5 (Часть 16): Идентификация линейных систем на основе обучения с учителем
Идентификация линейной системы может быть объединена с процессом обучения корректировке ошибки в алгоритме обучения с учителем. Это позволяет нам создавать приложения, основанные на методах статистического моделирования, не наследуя при этом уязвимость, связанную с ограничительными допущениями модели. Классические алгоритмы обучения с учителем имеют ряд ограничений, которые можно устранить, объединив эти модели с регулятором обратной связи, способным корректировать модель с учетом текущей рыночной конъюнктуры.
Упрощение работы с базами данных в MQL5 (Часть 2): Создание сущностей с помощью метапрограммирования Упрощение работы с базами данных в MQL5 (Часть 2): Создание сущностей с помощью метапрограммирования
Мы изучили расширенное использование #define для метапрограммирования в MQL5, создания сущностей, представляющих таблицы и метаданные столбцов (тип, первичный ключ, автоинкремент, возможность обнуления и т.д.). Мы централизовали эти определения в TickORM.mqh, автоматизировав генерацию классов метаданных и проложив путь для эффективной работы с данными в ORM без необходимости писать SQL вручную.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Моделирование рынка (Часть 23): Первые шаги на SQL (VI) Моделирование рынка (Часть 23): Первые шаги на SQL (VI)
В этой статье мы рассмотрим, как выполнить визуализацию и, следовательно, поймем, как структурирована база данных. Это было сделано с помощью анализа внутренней структуры базы данных. Хотя подобные вещи могут показаться излишними, они вполне оправданы, если мы действительно намерены стать администраторами баз данных. Да, есть люди, которые зарабатывают на жизнь, поддерживая и создавая базы данных.