Низкочастотные количественные стратегии в MetaTrader 5: (Часть 2) Бэктестинг lead/lag-анализа в SQL и MetaTrader 5
Введение
Вы, вероятно, уже слышали об «эффекте бабочки». Это простая для понимания метафора, популяризированная массовой культурой для иллюстрации теории хаоса. Вкратце она говорит, что взмах крыльев бабочки, например в Австралии, может вызвать землетрясение в Северной Америке. Это красивый образ, показывающий, что крошечные изменения в сложных системах, таких как погода, могут стать причиной огромных и непредсказуемых последствий.
Этот образ также прекрасно иллюстрирует центральную идею данной статьи и описываемого в ней анализа. Финансовые рынки похожи на сложные системы. Их часто моделируют как стохастические процессы, то есть процессы, в которых будущее состояние нельзя предсказать точно из-за влияния случайных факторов. Каждый тик, каждое изменение цены заключает в себе эти случайные факторы — так сказать, взмахи крыльев множества бабочек. Одни находятся рядом с изменением и оказывают прямое влияние; другие удалены и влияют косвенно. Но все они присутствуют и формируют цену.
Несмотря на эту неопределенность, специалисты в финансах пытаются проследить первопричины таких событий, как изменения цен. Они надеются, что понимание движущих факторов позволит им оценивать будущие исходы с разумными вероятностями. В этих поисках проявилась как минимум одна теперь уже очевидная истина: независимо от природы случайных факторов изменение распространяется на разные активы с разной скоростью. Поскольку время, необходимое изменению для распространения, может быть ценной информацией для лучшего понимания поведения вовлеченных активов, были разработаны методы его измерения. В этой статье мы рассмотрим один из таких методов.
Lead/Lag-анализ позволяет увидеть, опережают ли изменения цены актива A изменения цены актива B, то есть выступает ли актив A лидером, а актив B — последователем. Обычно между обоими активами уже установлена известная корреляционная или коинтеграционная связь, поэтому предполагается, что они движутся вместе. Но это не означает, что они всегда будут двигаться одновременно. Между движением A и движением B может существовать временной интервал. Характеризуя величину и частоту этого интервала, мы можем получить ценные знания о коррелированном или коинтегрированном портфеле и потенциально выявить торговые возможности возврата к среднему с очень низким риском.
Даже без известной корреляции или коинтеграции lead/lag-анализ может выявить паттерн лидер/последователь. В таком случае можно искать возможности для торговли по импульсу, но риск будет выше. В этой статье мы опишем первый сценарий — торговлю возврата к среднему по схеме лидер/последователь с коинтегрированными активами. Со временем сценарий импульсной торговли для некоррелированных активов станет темой будущей части.
Из-за этого выбора в статье используются техники, которые мы обсуждали в серии «Статистический арбитраж через коинтегрированные акции», в частности использование hedge ratio для рыночно-нейтральной портфельной торговли. В последней статье той серии мы привели причины, почему в конвейер статистического арбитража следует интегрировать систему, удобную для OLAP-аналитики, а в предыдущей статье этой серии, предложили для нее простую настройку. Теперь мы будем использовать эту систему для выполнения lead/Lag-анализа.
К концу статьи вы должны понимать, чего ожидать от низкочастотного lead/lag-анализа. Вы также узнаете, как построить анализатор на Python, избежать распространенных ошибок, интерпретировать результаты и выявлять торговые возможности.
Мы начинаем искать низкочастотные торговые возможности Lead/Lag, потому что именно там розничные трейдеры с обычным ноутбуком, стандартным сетевым подключением и ограниченным капиталом могут превратить небольшой масштаб в преимущество.
Что такое низкочастотный lead/lag-анализ?
Когда розничный трейдер видит выражение «lead/lag торговля», он почти автоматически может отвергнуть саму идею разработки торговой стратегии вокруг него. Это нормальная реакция. Lead/Lag-торговля обычно ассоциируется с высокочастотной торговлей (HFT), борьбой за микросекунды при маршрутизации ордеров, коллокацией в дата-центрах, высокопроизводительным кодом и, в конечном счете, опережением более медленных заявок. Для такой реакции есть веские причины, поскольку эксплуатация временных ценовых расхождений между коррелированными активами — или даже между одним и тем же активом на разных биржах — является самой понятной и популярной тактикой HFT-проп-десков.
Однако в низкочастотном lead/lag (LFLL) существуют реальные и часто упускаемые возможности. То, что считать низкой частотой, сильно зависит от точки зрения. Для свинг-трейдера это может быть месячный таймфрейм, а для DOM-скальпера — пятиминутный график. Поэтому с самого начала уточним: для наших целей высокочастотным является все, что ниже нескольких секунд. Мы будем искать торговые возможности на минутном таймфрейме и выше. Именно там можно разумно ожидать исполнения ордеров с управляемым проскальзыванием. На этих низких частотах преимущество можно искать в глубине и точности анализа, а не в скорости исполнения. На этих частотах любое разумное сетевое соединение делает задержку несущественной, а альфа должна приходить из способности лучше понимать путь и время распространения информации.
Нас интересует, например, время, за которое ценовое движение ведущей голубой фишки отрасли распространяется вниз по ее цепочке поставок, пока воздействие не дойдет до конкретного тикера; или мы можем анализировать эффект пост-отчетного дрейфа, когда объявление прибыли выше ожиданий может распространяться на некоторые тикеры в течение нескольких дней, пока крупные игроки корректируют позиции. В любом случае мы не будем бороться за миллисекунды. Мы будем находиться в диапазоне от часов до нескольких дней, где малый размер может быть преимуществом.
У каждого из этих примеров есть своя специфика — не только на этапе анализа данных, но и в торговых правилах. В следующие недели мы можем подробно обсудить некоторые из них. Пока же возьмем в качестве кейса время распространения от лидера цепочки поставок к последователю. Такой вид lead/lag-анализа часто называют — анализ кросс-активной информационной диффузии.
Построение анализатора
Анализ кросс-активной информационной диффузии включает расчет кросс-корреляции между лог-доходностями актива-лидера в момент t-n и лог-доходностями актива-последователя в момент t. Этот расчет кросс-корреляции может выявить Lead/Lag-связи, где движение цены актива-лидера предшествует движению цены последователя со статистически значимой регулярностью временного интервала.
Кросс-корреляция действительно является большой темой для специалистов, но для наших целей ее можно понять очень интуитивно. Согласно Wikipedia,
“математические понятия ковариации и корреляции очень похожи. Оба описывают степень, в которой две случайные величины или наборы случайных величин склонны отклоняться от своих ожидаемых значений похожим образом”.
В то время как коэффициент корреляции показывает, насколько две или более временные серии (наша история цен) склонны изменяться вместе, кросс-корреляция делает то же самое, но с временным смещением одной из этих серий. Корреляция вычисляет, «насколько» они движутся вместе, беря значения в один и тот же момент времени (например, в ту же минуту или секунду), а кросс-корреляция вычисляет это, применяя «лаг» к одной из них (например, пять минут или десять секунд назад).
Чтобы облегчить понимание понятия кросс-корреляции, полезно увидеть его на упрощенном, хорошо известном «наборе данных». Для этого мы создали скрипт, который генерирует синтетические данные (контролируемые значения) для лидера и последователя на 5-минутном таймфрейме (частота дискретизации). Цена лидера сдвинута во времени на 3 периода (15 минут), и кросс-корреляция рассчитывается для разных временных сдвигов (лагов) от 0 до 4. Переменная lag_0 хранит коэффициент кросс-корреляции при нулевом лаге, то есть без лага. lag_1 соответствует лагу в один период, то есть пять минут, и так далее. Поскольку мы знаем, что последователь отстает на 3 периода, ожидаем пик корреляции в lag_3.
--- Step 1: Synthetic Cross-Correlation Results --- Correlation lag_0_corr -0.031678 lag_1_corr -0.038247 lag_2_corr -0.016094 lag_3_corr 0.996679 lag_0_corr -0.031678 lag_1_corr -0.038247 lag_2_corr -0.016094 lag_3_corr 0.996679 lag_1_corr -0.038247 lag_2_corr -0.016094 lag_3_corr 0.996679 lag_2_corr -0.016094 lag_3_corr 0.996679 lag_4_corr -0.015409
Эти искусственные результаты говорят нам, что информационная диффузия занимает 15 минут. Если мы видим движение лидера сейчас и дискретизируем данные каждые 5 минут, можно ожидать, что последователь сдвинется через 3 периода, или через 15 минут.
Назначение этого простого скрипта — лишь показать, как должны выглядеть результаты кросс-корреляции: коэффициент должен достигать пика на лучшем лаге, указывая временной интервал, необходимый для того, чтобы цена последователя действительно последовала за ценой лидера. Скрипт приложен внизу статьи.
Как учитывать недетерминизм в SQL
В приведенном выше условном примере мы запрашиваем DataFrame Pandas всего со 100 точками данных, сдвинутыми относительно самих себя. В production мы запрашиваем Hive-партиционированное хранилище с тысячами или миллионами точек. Мы также объединяем как минимум две временные серии, которые должны быть идеально выровнены по времени, чтобы дать детерминированные результаты. Если мы запускаем один и тот же тест на одних и тех же данных, мы должны получать одинаковые результаты.
Однако в SQL-движках вроде DuckDB строки не обрабатываются в естественном порядке. документация DuckDB четко говорит:
“без [упорядочивания] результаты оконных функций общего назначения и чувствительных к порядку агрегатных функций, а также порядок фреймов не определены однозначно.”
Среди оконных функций общего назначения есть функции LEAD() и LAG(). Документация говорит, что простой SELECT без явного ORDER BY может каждый раз возвращать строки в другой последовательности. Если оконные функции вроде LAG() или CORR() применяются к неупорядоченному набору, корреляции и другие результаты будут меняться между запусками. Такой неопределенности нам не нужно.
Помимо общего SQL-понятия неупорядоченного множества, колонко-ориентированная модель хранения, которую использует DuckDB, хранит данные чанками для обеспечения массового параллелизма.
“Типичный запрос к хранилищу данных содержит несколько операций соединения, фильтрации, группировки и агрегации. Оптимизатор MPP [massively parallel processing] разбивает этот сложный запрос на ряд стадий выполнения и партиций, многие из которых могут выполняться параллельно на разных узлах кластера базы данных. Запросы, включающие сканирование больших частей набора данных, особенно выигрывают от такого параллельного выполнения.” (Mark Kleppmann, Designing Data-Intensive Applications, O’Reilly, 2017)
То есть база данных читает разные столбцы или чанки параллельно на разных ядрах CPU. Восстановленные или «составленные» строки поступают на этап оконной функции в том порядке, в котором закончилось их чтение. Без ORDER BY база данных просто обрабатывает их в порядке поступления.
Мы с партнером совершили эту ошибку, когда начинали самостоятельно, не имея прежнего опыта такого анализа в колонковых базах данных. Пока мы не поняли, что база данных как бы «перемешивает» записи, мы постоянно получали противоречивые результаты. Иногда это не так очевидно. Если не решить проблему, результаты могут стать ненадежными. Чтобы сделать проблему видимой, мы написали небольшой скрипт, имитирующий «перемешивание» записей на уровне базы данных.
Мы создали простую временную шкалу с пятью точками данных.
data = {
'time': pd.to_datetime(['10:00', '10:05', '10:10', '10:15', '10:20']),
'price': [100.0, 102.0, 101.0, 105.0, 104.0]
}
df = pd.DataFrame(data)
Затем, чтобы принудительно вызвать «сбой», физически перемешали строки.
df_shuffled = df.sample(frac=1, random_state=42).reset_index(drop=True)
Обратите внимание: хотя номера строк идут по возрастанию, столбец времени — нет.
--- Step 2: Visualizing the Non-Determinism Pitfall --- time price 0 2026-03-11 10:05:00 102.0 1 2026-03-11 10:20:00 104.0 2 2026-03-11 10:10:00 101.0 3 2026-03-11 10:00:00 100.0 4 2026-03-11 10:15:00 105.0
Когда мы вызываем функцию LAG(price) без ORDER BY, она получает отстающую цену в физическом порядке памяти:
# 3. THE DANGEROUS QUERY (Physical Order) query_broken = """ SELECT time, price, lag(price) OVER () as prev_price_broken FROM df_shuffled """
time price prev_price_broken 0 2026-03-11 10:05:00 102.0 NaN 1 2026-03-11 10:20:00 104.0 102.0 2 2026-03-11 10:10:00 101.0 104.0 3 2026-03-11 10:00:00 100.0 101.0 4 2026-03-11 10:15:00 105.0 100.0
Обратите внимание на выделенную вторую строку: цена 104.0 в 10:20. Какой должна быть предыдущая цена с логической точки зрения? Очевидно, это должна быть цена в строке 4, в 10:15, то есть 105.00. Но база данных возвращает цену из строки 0, в 10:05, то есть 102.0. Это не логическая предыдущая цена пять минут назад; это просто предыдущая физическая строка, предыдущая строка в памяти.
Что мы можем сделать, чтобы результаты были осмысленными и детерминированными? Начнем с вызова функции LAG() с предложением ORDER BY.
# 4. THE ROBUST QUERY (Logical Order) query_correct = """ SELECT time, price, lag(price) OVER (ORDER BY time ASC) as prev_price_correct FROM df_shuffled """
Затем она следует логическому порядку времени и возвращает правильную предыдущую цену в строке 3, время 10:15, то есть 105.0.
time price prev_price_correct 0 2026-03-11 10:00:00 100.0 NaN 1 2026-03-11 10:05:00 102.0 100.0 2 2026-03-11 10:10:00 101.0 102.0 3 2026-03-11 10:15:00 105.0 101.0 4 2026-03-11 10:20:00 104.0 105.0
В базе данных физический порядок НЕ является логическим порядком. Если мы явно не вызываем оконную функцию с ORDER BY, нельзя быть уверенными, что не выполняем логический запрос над физически упорядоченными данными. Поэтому это первая мера которую мы принимаем при построении lead/lag-анализатора: каждый расчет, включающий временной сдвиг (LAG), оборачивается в явное предложение OVER (ORDER BY time ASC).
Вторая мера заключается в том, что мы используем функцию row_number() для создания row_id для каждого тикера. Благодаря этому, даже если две свечи имеют одинаковую временную метку, их последовательность уже определена нами. Если у двух строк одинаковое время, база данных может перемешать их случайно. Поэтому мы устанавливаем канонический порядок, создавая уникальную, определенную последовательность для каждой строки.
SELECT time, ticker, close, -- Ensure every row has a unique, deterministic ID row_number() OVER (PARTITION BY ticker ORDER BY time ASC) as row_id
Когда мы рассчитываем лог-доходности, мы разбиваем данные по тикеру и обязательно сортируем по времени. Мы гарантируем, что результат LAG() для 10:05 всегда рассчитывается относительно 10:00, независимо от того, как данные хранятся на диске. Каждый LAG и каждое предложение OVER теперь явно используют ORDER BY time ASC, row_id ASC. Это заставляет движок выполнения следовать каноническому порядку, а не физическому порядку памяти. Мы никогда не позволяем базе данных самой решать порядок. Каждый раз, когда рассчитываем доходность или сдвиг, мы заново утверждаем хронологическое требование.
returns AS ( SELECT time, ticker, row_id, -- Returns must follow the canonical order log(close / lag(close) OVER (PARTITION BY ticker ORDER BY time ASC, row_id ASC)) as ret FROM filtered_data
Наконец, в качестве третьей меры, мы внимательно относимся к предложениям ‘JOIN’. Мы обеспечиваем строгое временное выравнивание по временным меткам лидера и последователя перед сдвигом доходностей лидера. Это нужно, чтобы мы всегда сравнивали одни и те же моменты времени. Возьмем, например, такое предложение JOIN:
JOIN ... ON l.time = c.time
Оно остается якорем, обеспечивая идеальную синхронизацию лидера и последователя во времени до вычисления любой кросс-корреляции. Здесь нужно быть внимательными, потому что у лидера и последователя могут быть разные часы торгов или пропущенные бары. Если поспешить и просто поставить строки рядом (строка 1 lead_asset против строки 1 follower_asset), можно сравнить утро понедельника у lead_asset с днем вторника у follower_asset. Такое временное выравнивание гарантирует, что мы сравниваем лидера и последователя только тогда, когда оба существуют в один и тот же момент.
joined AS ( SELECT l.time, l.ret as lead_ret, c.ret as lag_ret, l.row_id as lead_row_id FROM (SELECT * FROM returns WHERE ticker = '{lead_symbol}') l -- TEMPORAL ALIGNMENT: Sync the clocks of both assets JOIN (SELECT * FROM returns WHERE ticker = '{ticker}') c ON l.time = c.time ),
Это гарантирует, что соединение действует как синхронизированные часы. Оно отбрасывает любые несовпадающие данные и обеспечивает расчет корреляции только при сравнении в одной и той же временной зоне и торговой сессии.
Такого трехшагового подхода должно быть достаточно для получения детерминированных, воспроизводимых результатов, которые нам нужны. Я настоятельно рекомендую не игнорировать их при написании запросов или при запросе таких запросов у ИИ-ассистента.
Запрос диффузии
Предоставленный lead_lag_analyser.py следует трехэтапному конвейеру. Сначала мы синхронизируем локальное хранилище данных, как обычно. Скрипт вызывает класс DataDownloader, чтобы проверить наличие требуемых данных в локальном Hive-партиционированном хранилище, и при необходимости загружает недостающие бары.
Когда необходимые данные доступны локально, запрос диффузии фильтрует их по таймфрейму, периоду ретроспективы и необязательным часам сессии. Обратите внимание, что этот последний фильтр особенно важен при анализе не круглосуточных инструментов.
for ticker in candidates: query = f""" WITH filtered_data AS ( SELECT time, ticker, close, -- Ensure every row has a unique, deterministic ID row_number() OVER (PARTITION BY ticker ORDER BY time ASC) as row_id FROM market_view WHERE ticker IN ('{lead_symbol}', '{ticker}') AND tf = '{tf_str}' AND time >= '{start_date}' {session_filter} ), returns AS ( SELECT time, ticker, row_id, -- Returns must follow the canonical order log(close / lag(close) OVER (PARTITION BY ticker ORDER BY time ASC, row_id ASC)) as ret FROM filtered_data ), """
Обратите внимание на использование log(close/lag(close)) для расчета доходности каждого символа; то есть мы оцениваем кросс-корреляцию по лог-доходностям, а не по простым доходностям.
Как сказано выше о мерах, которые мы принимаем, чтобы не попасть в ловушку недетерминированного SQL-упорядочивания, мы выполняем JOIN по времени, чтобы обеспечить временное выравнивание перед расчетом кросс-корреляции.
joined AS ( SELECT l.time, l.ret as lead_ret, c.ret as lag_ret, l.row_id as lead_row_id FROM (SELECT * FROM returns WHERE ticker = '{lead_symbol}') l -- TEMPORAL ALIGNMENT: Sync the clocks of both assets JOIN (SELECT * FROM returns WHERE ticker = '{ticker}') c ON l.time = c.time ),
Каждая доходность лидера сдвигается на N периодов. Максимальное число периодов лага является параметром метода analyze_diffusion.
def analyze_diffusion(self, lead_symbol, candidates, timeframe, lookback_days, max_lag_periods=10, session_start=None, session_end=None): """ Analyzes information diffusion by checking cross-correlation at various lags. """ (...) lagged AS ( SELECT time, lead_ret, lag_ret, -- Shifting the lead returns deterministically {' , '.join([f"lag(lead_ret, {i}) OVER (ORDER BY time ASC, lead_row_id ASC) as lead_lag_{i}" for i in range(1, max_lag_periods + 1)])} FROM joined )
Наконец, запрос вычисляет коэффициент корреляции Пирсона для каждого лага одновременно и гарантирует, что мы получаем результаты кросс-корреляции только для тех наблюдений, где доходности лидера и последователя не равны NULL.
"""
SELECT
corr(lead_ret, lag_ret) as corr_0,
{' , '.join([f"corr(lead_lag_{i}, lag_ret) as corr_{i}" for i in range(1, max_lag_periods + 1)])},
count(*) as sample_size
FROM lagged
WHERE lead_ret IS NOT NULL AND lag_ret IS NOT NULL
""" Если вы следите за этой серией статей о статистическом арбитраже, то, вероятно, помните, что при анализе коэффициента корреляции Пирсона между двумя активами для парной торговли, мы искали положительные (или отрицательные) коэффициенты корреляции выше 0.80, в идеале выше 0.90. В Lead/Lag-исследовании мы ищем очень слабые сигналы. Корреляция 0.05 или 0.10 может оказаться значимой — или нет — главным образом в зависимости от размера выборки, то есть числа анализируемых периодов (баров).
Чтобы не оставлять значимость этого коэффициента полностью на субъективные критерии, анализатор включает тест значимости на основе квадратного корня из размера выборки.
# Significance threshold (rough estimate: 2/sqrt(N)) significance = 2.0 / np.sqrt(sample_size)
Он прост, но работает. Для небольшой выборки, скажем 100 периодов, порог значимости равен 2 / 10 = 0.20. В таком случае корреляция 0.08 незначима, то есть это просто шум. С другой стороны, при 10 000 периодов порог равен 2 / 100 = 0.02, и корреляция 0.08 уже может быть очень значимой.
Если корреляция на конкретном лаге ниже порога значимости, связь, скорее всего, является шумом, и первым шагом должно быть увеличение размера выборки, прежде чем отбрасывать пару или пытаться торговать ее.
Хотя эта простая и известная формула помогает фильтровать шум, важно помнить, что наш lead/lag-анализ полностью основан на кросс-корреляции, которая является частным случаем корреляции Пирсона. Как статистический тест, корреляция Пирсона имеет доверительный интервал, а это значит, что если вы тестируете 100 разных кандидатов в последователи против одного лидера, то по законам вероятности как минимум у 5 из них значимая корреляция может появиться чисто случайно. Статистики называют это ошибкой I рода.
Кроме того, доходности цен не являются идеально случайными, независимыми и одинаково распределенными (I.I.D.) переменными. Если у актива есть импульс, это может искусственно завысить корреляцию. В таком случае наш тест значимости может переоценивать реальную надежность связи.
Используйте разумный размер выборки (эмпирическое правило: >500 баров). Не тестируйте десятки кандидатов за один запуск без поправки на множественное тестирование. Не торгуйте в реале до обширных out-of-sample бэктестов и мониторинга стабильности.
Эффект Эппса
Прежде чем перейти к главному примеру статьи, я хотел бы обратить ваше внимание на явление, известное как эффект Эппса которое может сбивать с толку, когда вы начнете изменять параметр таймфрейма. Оно описывается как явление, при котором
“эмпирическая корреляция между доходностями двух разных акций уменьшается с длиной интервала, за который измеряются изменения цены.”
Предположим, мы ищем Lead/Lag-возможности на товарном рынке, точнее в секторе добычи. Мы хотим узнать, существует ли разрыв в движении цены лидер-последователь между золотом и некоторыми связанными с золотом бумагами. В качестве лидера берем XAUUSD. В качестве кандидатов-последователей мы выбрали акцию горнодобывающей корпорации (NEM.US) и два ETF, связанных с золотом (SGOL.US и GDX.US).
Затем мы запускаем анализатор без фильтрации по времени сессии на таймфрейме H1 с 30-дневным периодом ретроспективы (размер выборки очень мал, но пока это можно игнорировать, поскольку для понимания эффекта Эппса это несущественно).
lead_lag_analyser.py Analyzing Information Diffusion for XAUUSD (H1, 30 days)... === Diffusion Ranking (Best Non-Zero Lag) === Ticker Lag_0_Corr Best_Lag Best_Lag_Corr Is_Significant Sample NEM.US 0.6169 10 0.2155 True 119 GDX.US 0.6968 10 0.1975 True 119 SGOL.US 0.7168 10 0.1899 True 119 Top Candidate: NEM.US Correlation at Lag 10 (600 mins): 0.2155 >>> CONTEMPORANEOUS: Moves mostly in sync with the lead asset.
Судя по результатам, можно заключить, что все они движутся синхронно, потому что десятичасовой лаг, вероятно, связан с отсутствием фильтра по времени сессии. Лаг одинаков для трех символов, с относительно высокой корреляцией на десятом часу. Поскольку XAUUSD торгуется 24 часа OTC, а остальные три символа торгуются только в часы работы биржевого рынка, они, вероятно, «следуют за лидером», когда их рынок открыт. Такая интерпретация выглядит логичной.
Также результаты говорят, что для этих символов рынок не показывает неэффективности на таймфрейме H1. GDX и NEM представляют акции золотодобывающих компаний, а высокая корреляция на Lag_0 говорит, что они реагируют на изменения спотовой цены золота в течение того же часа. Вероятно, это уже арбитражируется HFT и другими участниками. Что если изменить таймфрейм на 5 минут?
Analyzing Information Diffusion for XAUUSD (M5, 30 days)... === Diffusion Ranking (Best Non-Zero Lag) === Ticker Lag_0_Corr Best_Lag Best_Lag_Corr Is_Significant Sample NEM.US 0.4576 6 0.0520 False 1099 GDX.US 0.5217 6 0.0486 False 1099 SGOL.US 0.6450 6 0.0446 False 1099 Top Candidate: NEM.US Correlation at Lag 6 (30 mins): 0.052 >>> CONTEMPORANEOUS: Moves mostly in sync with Lead.
При переходе с часового на пятиминутный таймфрейм мы видим резкое падение корреляций на лагах. Это эффект Эппса на практике. Однако помимо известного статистического явления, поскольку мы не используем фильтр времени сессии, разные часы работы рынков вносят вклад в исчезающую корреляцию, которую мы видим на таймфрейме M5. Валютная пара XAUUSD очень активна во время лондонской сессии, тогда как SGOL.US и GDX.US стоят на месте, потому что рынки США закрыты. Когда мы рассчитываем корреляцию по 24-часовому окну на M5, мы включаем тысячи баров, где XAUUSD быстро движется, а горнодобывающие бумаги и ETF ждут открытия рынка. Это существенно размывает общий коэффициент корреляции.
Обязательно используйте фильтр времени сессии, чтобы минимизировать это искажение. Используйте параметры session_start и session_end, чтобы сосредоточиться на основных торговых часах активов. Если Lag_0_Corr значительно выше всех лаговых корреляций, как в обоих примерах выше, активы движутся синхронно. Настоящий последователь с лагом покажет отчетливый пик корреляции на ненулевом лаге. Мы увидим много таких примеров в следующем анализе.
Кроме того, при тестах на относительно очень высоких и очень низких таймфреймах убедитесь, что число наблюдений на самом высоком таймфрейме достаточно велико. Как сказано выше, > 500 баров должно быть достаточно для статистической уверенности.
Взгляд на коинтегрированные акции
Мы уже провели несколько экспериментов с коинтегрированными акциями. Мы знаем, что в долгосрочной перспективе они склонны двигаться вместе. В наших экспериментах мы взяли NVDA (Nvidia Co.) как лидера полупроводниковой отрасли, и ранжировали несколько связанных с полупроводниками акций по силе их коинтеграции с NVDA. Поэтому мы знаем, что они склонны двигаться вместе, знаем, что есть лидер, а также есть некоторые «последователи». Что если у некоторых из этих последователей есть постоянный, измеримый лаг движения цены относительно движения цены NVDA? Это разумная гипотеза для проверки, не так ли?
Этот пример был специально подобран, чтобы проиллюстрировать главную цель lead/lag-анализа.
Мы взяли акции, наиболее высоко ранжированные по коинтеграции с NVDA, и протестировали их на таймфрейме M5 с периодом ретроспективы 60 дней.
Analyzing Information Diffusion for NVDA (M5, 60 days)... === Diffusion Ranking (Best Non-Zero Lag) === Ticker Lag_0_Corr Best_Lag Best_Lag_Corr Is_Significant Sample ASX 0.4637 1 0.0933 True 3162 AVGO 0.5514 1 0.0640 True 3199 MU 0.4594 2 0.0522 True 3199 AMD 0.4345 2 0.0423 True 3199 LAES 0.3179 9 0.0336 False 3199 INTC 0.2617 2 0.0330 False 3199 NVTS 0.4348 2 0.0329 False 3199 Top Candidate: ASX Correlation at Lag 1 (5 mins): 0.0933 >>> CONTEMPORANEOUS: Moves mostly in sync with Lead.
Мы ожидали, что результаты идентифицируют их как ‘contemporaneous’ (неинформативные, что было бы идеальным). В конце концов, они коинтегрированы. Это ясно видно по высоким корреляциям на Lag_0, которые значительно выше лаговых корреляций. Это говорит, что рынок здесь очень эффективен. Большая часть движения цены NVDA почти мгновенно, в пределах того же 5-минутного бара, закладывается в ASX или AVGO.
В столбце Best Lag есть две группы последователей:
- лаг 5 минут (Best_Lag 1). Эти акции наиболее чувствительны к движению цены NVDA. Значимая корреляция на Lag 1 означает, что если NVDA делает сильное движение, через 5 минут в ASX и AVGO есть статистически измеримое эхо. Если NVDA пробивается, у нас есть 5 минут, чтобы проверить, отреагировали ли ASX или AVGO.
- лаг 10 минут (Best_Lag 2) — у этих бумаг немного более длинный период. Их пиковая корреляция возникает через 10 минут после движения NVDA.
Причины различия временного интервала между этими двумя группами здесь не важны. Все, что мы хотим знать, — существует ли лаг и пригоден ли он для торговли. Обратите внимание: хотя ASX выглядит главным кандидатом, сигнал лучшего лага слаб. Относительно небольшая корреляция 0.0933 на Lag 1 означает, что прошлое движение NVDA объясняет только около 9% текущего движения ASX. Можно сказать, что это лишь вероятностное преимущество. Мы не стали бы торговать по одному лишь этому сигналу, но его можно использовать как инструмент подтверждения, если мы уже собирались открыть позицию по ASX. Внезапный скачок NVDA около 5 минут назад может быть зеленым светом.
Главный кандидат ASX — учебный пример того, почему мы называем это анализом информационной диффузии цепочки поставок. ASX часто следует за NVDA с небольшим лагом, потому что это поставщик услуг сборки и тестирования полупроводников. NVDA проектирует чипы. ASX предоставляет услуги бэкенд-производства. Лидерство спроса NVDA за несколько минут распространяется вниз к поставщикам услуг.
Хотя рынок на 90% эффективен (contemporaneous), мы нашли паттерн, который может заслуживать дальнейшего исследования или даже бэктеста. Возможно, мы выявили потенциальную торговую возможность. Как ее исследовать? Возможно, начнем искать дивергенцию, предполагающую последующую конвергенцию. Имеет ли это смысл?
Нет, потому что отсутствует тот пик корреляции на ненулевом лаге, о котором мы говорили выше: “Настоящий последователь с лагом покажет отчетливый пик корреляции на ненулевом лаге.”
Пара XAUUSD (спот золота) и ETF GDX.US показывает именно это свойство за последние 30 дней на минутном таймфрейме.
Syncing Lead: XAUUSD [XAUUSD] No local data. Starting from 2026-03-01 02:36:52.562940+00:00 Analyzing Information Diffusion for XAUUSD (M1, 30 days)... === Diffusion Ranking (Best Non-Zero Lag) === Ticker Lag_0_Corr Best_Lag Best_Lag_Corr Is_Significant Sample GDX.US 0.0764 1 0.0843 True 6198 Top Candidate: GDX.US Correlation at Lag 1 (1 mins): 0.0843 >>> INFORMATIVE LEAD: This asset follows with a distinct delay.
Именно это мы ищем, чтобы обосновать настоящий бэктест в MetaTrader 5.
Бэктестинг
Прежде чем разрабатывать полнофункциональный советник для бэктеста, мы можем проверить, была бы связь лидер/последователь между XAUUSD и GDX.US прибыльной, запустив чистый SQL-бэктест. Советник MetaTrader 5 — последний этап нашего конвейера, когда у нас уже есть стратегия, достойная усилий, связанных с разработкой EA. Метод чистого SQL-бэктеста менее распространен в нашем сообществе. Он может быть полезен, чтобы показать практические преимущества OLAP-системы, которую мы представляем в этой серии.
Чистый SQL-бэктест очень ограничен по сложности торговых правил, которые можно тестировать, но это самый простой в реализации и самый быстрый в запуске способ. Поэтому он хорошо подходит для начала конвейера, когда нужно просмотреть десятки, сотни или даже тысячи символов. Эта техника дает мощный инструмент предварительного скрининга, позволяющий масштабировать бэктест стратегии с одной пары на целый сектор без усложнения кода.
Стоит отметить, что независимо от связи лидер/последователь мы должны учитывать различия между торговлей парами или корзинами с коинтеграционной связью и без нее, как в случае пары XAUUSD/GDX.US, которую мы используем здесь в качестве примера.
Коинтегрированные пары или корзины: возврат к среднему
Связь лидер/последователь не привязана к коинтеграции. Она указывает только на лаговую кросс-корреляцию и может возникать там, где коинтеграции нет. Но если коинтеграция есть, она влияет на то, как можно торговать парой или корзиной. Когда lead/lag-связь существует вместе с коинтеграцией, мы имеем систему возврата к среднему. Коинтеграция означает, что, хотя два актива могут расходиться, у них есть долгосрочное равновесие, поэтому любое расхождение в lead/lag-связи рассматривается как временное. Если лидер движется, а последователь — нет, мы знаем, что с высокой вероятностью последователь в итоге последует за лидером и сохранит статистическую связь.
Риск постоянного расхождения низок. Это рыночно-нейтральный способ для коинтегрированных портфелей. Мы устраняем рыночный риск, покупая/продавая последователя и лидера всегда в противоположных направлениях. Объем ордеров определяется hedge ratio, полученным из собственного вектора Йохансена или простой обычной МНК-регрессии (OLS).
Некоинтегрированные пары или корзины: импульс
Но наличие связи лидер/последователь позволяет торговать простой направленный тренд без хеджирования. Если лидер сдвинулся, а последователь остался на месте, можно ставить на то, что последователь двинется в том же направлении в ожидаемый интервал времени. Это более рискованный сценарий. Здесь лидер предсказывает направление или импульс последователя, но они не обязательно "идут вместе". Лидер дает направленный сигнал, но два актива со временем могут расходиться бесконечно далеко. Это чисто импульсная торговля. Мы не торгуем возврат к среднему. Мы не можем спокойно ждать, пока захеджированная пара сойдется. Если последователь не реагирует, убыток может быть бесконечным, потому что спред не стационарен. Поэтому обязательны жесткий стоп-лосс, выход по времени или закрытие по противоположному сигналу. Кроме того, объем ордера не определяется статистическим измерением. Он произволен, что увеличивает риск.
Бэктестинг с помощью чистого SQL
OLAP-система в начале конвейера позволяет анализировать большие исторические наборы данных за секунды, даже офлайн. Мы должны уметь искать паттерны, повторения и аномалии сразу в большом числе символов и получать ответ за несколько секунд на обычном ноутбуке. Мы хотим тестировать десятки комбинаций символов для парной торговли и перестановки коинтегрированных корзин, а затем повторно запускать релевантные варианты в цикле каждые пять минут или каждую минуту для мониторинга торговли, а в итоге — для обновления параметров советника, работающего в реальной торговле. Все это должно быть простым, повторяемым, точным и быстрым.
Чистый SQL-бэктест прекрасно показывает, как можно достичь этой цели. Однако у него есть ограничения: в чистом SQL может быть трудно тестировать сложные торговые правила. Подробная отчетность и построение графиков, которые бесплатно доступны в MetaTester, могут потребовать внешних инструментов. Некоторые статистические функции могут отсутствовать в системе по умолчанию. Их можно реализовать как пользовательские функции (UDF) в соответствии с требованиями конкретной системы, но это ограничение нужно учитывать. При разработке количественных стратегий мы постоянно зависим от статистических функций. Пример такого ограничения мы увидим прямо сейчас. Несмотря на ограничения, это самый быстрый способ тестировать новые идеи, когда ограничения не мешают, как в случае lead/lag-анализа.
Когда исторические данные доступны и система настроена, мы можем проверять гипотезы только с помощью наших данных и нескольких строк SQL.
К этой статье приложен lead_lag_backtest.sql. Поскольку наша цель — не учить SQL и не писать учебник, мы не будем описывать его пошагово. Вместо этого хотим дать общий взгляд на структуру и, что важнее, показать, как она связана с торговыми правилами, которые мы будем использовать при переходе к бэктесту на основе советника. То есть мы хотим использовать этот скрипт как инструмент для понимания принципов lead/lag-бэктеста, применимых к любому методу.
lead_lag_backtest.sql
Первое, что нужно отметить: это векторизованный бэктест, то есть он рассчитывает все строки параллельно, а не в циклах по каждому бару, как в событийном фреймворке бэктеста. Это дает скорость и низкие требования к памяти, но скрипт ожидает, что минутные цены уже доступны в нашем Hive-партиционированном локальном хранилище.
В этом примере мы используем XAUUSD (спот золота) как актив-лидер и золотой ETF GDX.US как актив-последователь (или «laggard»). Тестируемая стратегия предполагает, что цена ETF следует за спотовой ценой с 5-минутным лагом.
Когда две ценовые серии выровнены по временным меткам, спотовая цена золота (лидер) сдвигается назад на пять периодов, в данном случае на пять минут. В этот момент у нас есть три выровненные временные серии: цена закрытия ETF, цена закрытия спотового золота и сдвинутая цена закрытия спотового золота. Таблица иллюстрирует это сдвинутое выравнивание.
| time | закрытие GDX.US | закрытие XAUUSD | сдвинутое закрытие XAUUSD |
|---|---|---|---|
| 5 | 2 | 15 | 17 |
| 10 | 4 | 17 | 19 |
| 15 | 6 | 19 | 21 |
| 20 | 8 | 21 | ?? |
Таблица 1 — упрощенное представление трех выровненных временных серий
Затем скрипт рассчитывает скользящий hedge ratio. В предыдущих примерах мы использовали собственные векторы теста коинтеграции Йохансена для получения hedge ratio. Здесь это заменено обычной регрессией наименьших квадратов (OLS), предоставляемой функцией REGR_SLOPE().
REGR_SLOPE(lagg_close, lead_shifted)
Здесь lead_shifted — независимая переменная регрессии, то есть переменная, используемая для «предсказания» lagg_close, которая является зависимой переменной. Функция возвращает скользящий hedge ratio, обычно обозначаемый как Beta. Умножив сдвинутую цену лидера на Beta, мы получаем ожидаемую цену последователя.
Ожидаемая цена последователя = beta * lead_shifted
Поскольку между ожидаемой ценой последователя и фактической ценой последователя есть разница, у нас появляется остаток.
Остаток = lagg_close - (beta * lead_shifted)
Остаток — это значение, которое мы используем для генерации сигналов, отклонение от ожидаемого значения. Затем мы рассчитываем среднее и дисперсию остатка для каждой временной метки (скользящее среднее и скользящую дисперсию) за период ретроспективы. Они будут использоваться для расчета z-score.
z-score: (residual - r_mean) / SQRT(r_var)
Z-score показывает, на сколько стандартных отклонений текущий остаток (остаток в конкретной временной метке) находится от своего скользящего среднего. Это работает как стандартный индикатор, сообщая о возможных состояниях перекупленности/перепроданности. Скрипт использует z-score 2.0 как порог. На основе нарушений этого порога скрипт генерирует торговые сигналы.
Если z-score равен 0, отклонения от скользящего среднего остатка нет. Если он < 0, последователь отстал и «перепродан». И наоборот, если z-score > 0, лидер упал, а последователь снова отстал, но теперь он «перекуплен». В любом случае, когда z-score выше +2.0 или ниже порога -2.0, мы соответственно продаем «перекупленного» или покупаем «перепроданного» последователя.
Как видно, торговые правила очень похожи на обычную стратегию возврата к среднему для коррелированных пар. Мы всегда ожидаем, что отклонения вернутся к среднему. Разница в том, что у нас меньше статистических доказательств, что это произойдет.
Наконец, скрипт рассчитывает лог-доходности для каждого бара.
target_pos * (LN(lagg_close) - LN(LAG(lagg_close)))
Это вычисляет прибыль/убыток от удержания позиции в течение каждой минуты, предполагая, что позиция мгновенно корректируется на основе сигнала. То есть это показывает, сколько мы заработали бы или потеряли, если бы имели открытую позицию на всех сигналах.
В конце скрипт агрегирует результаты по всем барам, где доходности не были null.

Рис. 1. Снимок результатов lead_lag_backtest.sql для спота золота x ETF, связанного с золотом
Стоит отметить, что при таком векторизованном подходе разница между запуском бэктеста для простой пары, как выше, и запуском для десяти, двадцати или двухсот символов становится пренебрежимо малой. Мы можем легко просматривать десятки символов одновременно в одном запросе.
Бэктестинг с советником MQL5
Теперь, когда мы отфильтровали кандидатов лидер/последователь из десятков символов, можно разработать более надежный и производительный бэктест в MetaTrader 5 и использовать его возможности оптимизации. Помните, что lead/lag-возможности могут быть крайне эфемерными. Нередко пара отлично работает одну неделю, а на следующей связь уже нарушена. Это требует постоянного мониторинга и обновления портфеля. Самый эффективный путь к обновлению портфеля — чистый SQL-бэктест, который мы видели выше.
Торговый советник приложен внизу статьи для ваших тестов и экспериментов. Я хотел бы лишь обратить внимание на некоторые части кода.
//+------------------------------------------------------------------+ //| Lead-Lag.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ (..) //+------------------------------------------------------------------+ //| inputs | //+------------------------------------------------------------------+ input string InpLeaderSymbol = "XAUUSD"; // Leader input string InpLaggardSymbol = "GDX.US"; // Laggard (trade one side) input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M1; input int InpLagBars = 5; // Leader shift input int InpWindow = 60; // Rolling window input double InpZOpen = 2.0; input double InpZClose = 0.5; input double InpLot = 1.0; input int InpSlippage = 5; input int InpMagic = 20260325;
Среди параметров торгового советника, главные кандидаты для оптимизации — InpWindow, InpZOpen и InpZClose. В нашей оптимизации (см. ниже) мы использовали скользящее окно и порог z-score для закрытия. Не следует менять InpLagBars, потому что именно этот lead/lag анализ указал как статистически значимый.
//+------------------------------------------------------------------+ //| OnTick Function | //+------------------------------------------------------------------+ void OnTick() { (...) // 1) fetch aligned close arrays for laggard and leader int bars = MathMax(InpWindow + InpLagBars + 5, 200); double laggClose[]; double leadClose[]; ArraySetAsSeries(laggClose, true); ArraySetAsSeries(leadClose, true); if(CopyClose(InpLaggardSymbol, InpTimeframe, 0, bars, laggClose) <= 0 || CopyClose(InpLeaderSymbol, InpTimeframe, 0, bars, leadClose) <= 0) { return; }
Необходимое выравнивание цен двух символов реализовано с помощью встроенной функции MQL5 CopyClose.
// 2) Build shifted leader series (lag by InpLagBars) double leadShifted[]; ArrayResize(leadShifted, ArraySize(leadClose)); for(int i = 0; i < ArraySize(leadClose); i++) leadShifted[i] = (i + InpLagBars < ArraySize(leadClose)) ? leadClose[i + InpLagBars] : 0.0;
Лаг в 5 баров реализован добавлением значения InpLagBars к индексу массива leadClose.
// 3) compute rolling regression slope (beta), residual, mean, var, zscore int idx = 0; // current bar index 0 if(ArraySize(laggClose) < InpWindow + InpLagBars + 1) return; int valid = InpWindow; // compute beta at current bar (using most recent up to InpWindow) double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0; int n = 0; for(int j = idx; j < idx + InpWindow; j++) // AsSeries so 0==latest { double x = leadShifted[j]; double y = laggClose[j]; if(x == 0.0) continue; // skip misaligned values sumX += x; sumY += y; sumXY += x * y; sumX2 += x * x; n++; } if(n < 2) return; double beta = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
В советнике встроенная функция REGR_SLOPE(), которую мы использовали для получения Beta в SQL, заменена простой моделью линейной регрессии.
// rolling residual stats over InpWindow double mean = 0, var = 0; int count = 0; for(int k = idx; k < idx + InpWindow; k++) { double x = laggClose[k] - beta * leadShifted[k]; if(!IsFiniteDouble(x)) continue; mean += x; var += x * x; count++; } if(count < 2) return; mean /= count; var = var / count - mean * mean; if(var < 1e-12) var = 1e-12; double zscore = (resid - mean) / MathSqrt(var); // 4) position logic double wantPos = 0.0; if(zscore < -InpZOpen) wantPos = +1.0; else if(zscore > +InpZOpen) wantPos = -1.0; else if(MathAbs(zscore) < InpZClose) wantPos = 0.0; else { if(positionLong) wantPos = 1.0; if(positionShort) wantPos = -1.0; } ManagePosition(wantPos); }
Логика входа/выхода основана на пороге z-score, заданном во входных параметрах InpZOpen и InpZClose. Переменная wantPos +1 означает купить laggard/последователя; wantPos -1 означает продать его; wantPos 0 означает ничего не делать. InpZOpen и InpZClose — основные значения по умолчанию, подлежащие оптимизации, вместе с InpWindow.
//+------------------------------------------------------------------+ //| Close positions by opposite signal | //+------------------------------------------------------------------+ void ManagePosition(double wantPos) { int total = PositionsTotal(); bool hasLong = false; bool hasShort = false; for(int i = 0; i < total; i++) { ulong posTicket = PositionGetTicket(i); if(PositionGetString(POSITION_SYMBOL) != InpLaggardSymbol) continue; ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); if(ptype == POSITION_TYPE_BUY) hasLong = true; if(ptype == POSITION_TYPE_SELL) hasShort = true; } // close opposite positions if(wantPos == 0.0) { if(hasLong) ClosePosition(POSITION_TYPE_BUY); if(hasShort) ClosePosition(POSITION_TYPE_SELL); positionLong = positionShort = false; return; } if(wantPos > 0 && !hasLong) { if(hasShort) ClosePosition(POSITION_TYPE_SELL); OpenPosition(POSITION_TYPE_BUY); positionLong = true; positionShort = false; } else if(wantPos < 0 && !hasShort) { if(hasLong) ClosePosition(POSITION_TYPE_BUY); OpenPosition(POSITION_TYPE_SELL); positionLong = false; positionShort = true; } }
Позиции закрываются противоположным сигналом.
Это настройки, использованные в бэктесте. Соответствующий файл *.set приложен.

Рис. 2. Настройки MetaTester для lead/lag-бэктеста XAUUSD (спот золота) и ETF GDX.US

Рис. 3. Статистика MetaTester для lead/lag-бэктеста XAUUSD (спот золота) и ETF GDX.US.

Рис. 4. График MetaTester для lead/lag-бэктеста XAUUSD (спот золота) и GDX.US ETF

Рис. 5. Время сделок MetaTester для lead/lag-бэктеста XAUUSD (спот золота) и ETF GDX.US
Обратите внимание, что мы торговали только во время сессии США из-за GDX.US, который торгуется преимущественно на NYSE. Также, возможно, следует протестировать фильтр дней недели для торговли только по средам.

Рис. 6. Корреляция MetaTester (MFE, MAE) для lead/lag-бэктеста XAUUSD (спот золота) и ETF GDX.US

Рис. 7. Время удержания позиций MetaTester для lead/lag-бэктеста XAUUSD (спот золота) и ETF GDX.US
Оптимизация
Мы оптимизировали период ретроспективы скользящего окна и порог z-score для закрытия позиций. Это были входные параметры. Соответствующий файл *.ini приложен.

Рис. 8. Входные параметры оптимизации MetaTester для lead/lag-бэктеста XAUUSD (спот золота) и ETF GDX.US
Это были результаты оптимизации. Мы выбрали первый вариант (pass 10), чтобы запустить одиночный тест с результатами выше.

Рис. 9. Результаты оптимизации MetaTester для lead/lag-бэктеста XAUUSD (спот золота) и ETF GDX.US
Пожалуйста, учитывайте, что результаты не включают транзакционные издержки (комиссии, свопы XAUUSD и т. д.).
Заключение
В этой статье мы представили полный конвейер lead/lag-анализа: от обнаружения связи лидер/последователь, через фильтрацию чистым SQL-бэктестом, до финального бэктеста в MetaTrader 5.
Мы использовали векторизованное выполнение колонковой базы данных и строгое упорядочивание оконных функций, чтобы выполнять lead/lag-скрининг по десяткам активов за секунды. Такой детерминированный подход гарантирует воспроизводимость результатов исследования, что необходимо любой количественной торговой стратегии.
Мы сосредоточились на трех распространенных ошибках в запросах lead/lag-диффузии: отсутствие ORDER BY в оконных функциях, отсутствие детерминированной нумерации строк и несоответствие временных меток между сериями.
Наконец, мы использовали выборку известных и уже ранжированных коинтегрированных акций, чтобы показать очень краткий, но практический пример анализа, который мог выявить потенциальную торговую возможность. Однако отсутствие пика корреляции на ненулевом лаге указывало, что лаг не пригоден для торговли. Поэтому мы выбрали пару, у которой за последние 30 дней был такой пик корреляции на ненулевом лаге, и запустили бэктест за последние две недели, оптимизировав период скользящих окон и порог z-score выхода из позиции для лучших результатов.
| Название файла | Описание |
|---|---|
| cross_correlation_synth.py | Python-скрипт для имитации кросс-корреляции на синтетических данных |
| non_determinism_synth.py | Python-скрипт для имитации недетерминированного SQL-упорядочивания на синтетических данных |
| lead_lag_analyser.py | Python-скрипт для запуска lead/lag-анализа |
| lead_lag_backtest.sql | SQL-файл для запуска чистого SQL-бэктеста |
| Lead_Lag.mq5 | Исходный файл советника MQL5 |
| tester-set-lead-lag-xauusd-gdxus.ini | INI-файл настроек тестера |
| expert-set-lead-lag-xauusd-gdxus-opt-params.set | SET-файл параметров оптимизации |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/21811
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Архитектура машинного обучения для MetaTrader 5 (Часть 9): Интеграция байесовской оптимизации гиперпараметров в производственный пайплайн
Автоматизация торговых стратегий в MQL5 (Часть 29): Создание системы торговли по гармоническому паттерну "Гартли" на основе Price Action
Архитектура машинного обучения для MetaTrader 5 (Часть 8): Байесовская оптимизация гиперпараметров с Purged Cross-Validation и ранним отсечением испытаний
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования