Блог им. empenoso
Последние две недели я публиковал подборки из рубрики Traders’ Tips журнала Technical Analysis of STOCKS & COMMODITIES за 2001-2005 и 2006-2010 годы. Спасибо за ваши комментарии — от ироничных “опять комиксы?” до вполне серьёзных вопросов о практическом применении и бэктестах. Именно они побудили меня подойти к делу иначе.
Вместо очередного обзора я решил сосредоточиться на одной идее: реализовать её на Pine Script для TradingView и протестировать на фьючерсах с Московской Биржи. Кстати, Traders’ Tips — это не отдельное приложение, а рубрика в журнале. Но суть не в этом: её практическая ценность по-прежнему велика.
В центре внимания — случайно выбранная статья Барбары Стар “Confirming Price Trend” (S&C, декабрь 2007). Почему именно она? Подтверждение тренда остаётся актуальной задачей, а методы вроде линейной регрессии и R² доступны для понимания и применимы на дневных и часовых графиках.
В статье — теория этой стратегии, код на Pine Script, результаты тестирования и выводы.
Представьте, что вы едете по дороге. Линейная регрессия — это как прямая трасса, проведённая по данным: она показывает общий курс, игнорируя кочки и повороты. Цена движется вокруг этой «дороги», а наклон линии подсказывает, в какую сторону едем — вверх или вниз.
Теперь про R-squared: это как индикатор сигнала Wi-Fi. Если он близок к 1 — цена чётко держится вдоль регрессии, тренд сильный. Если ближе к нулю — «связь» теряется, рынок шумный, направление неясное.
Почему это круче скользящих средних? У регрессии меньше запаздываний и она точнее «схватывает» развороты. А R-squared помогает не гадать — есть тренд или просто случайное движение. Вместе они дают простую, но мощную систему фильтрации шума и подтверждения тренда.
Начнём с периода: 10 баров — для поиска коротких импульсов и ранних сигналов, 20 баров — сбалансированный вариант для дневных графиков, 50 баров — для фильтрации шума и работы по тренду. Выбор зависит от стиля торговли и таймфрейма.
R-squared помогает определить фазу тренда:
его рост с низких уровней (например, с 0.1 до 0.3) часто указывает на начало движения;
падение с высоких (например, с 0.6 до 0.3) — признак ослабления и возможного флэта.
Ориентиры:
для 20 баров значим уровень 0.20;
для 10: 0.40;
для 50: достаточно 0.08.
Наклон линии регрессии (slope) показывает направление:
выше нуля — тренд вверх;
ниже — вниз.
Вместе с R² он позволяет отличить уверенное движение от случайного шума и избегать ложных входов.
Инструмент: TradingView и Pine Script
Для реализации стратегии я выбрал TradingView. Это один из самых доступных инструментов для быстрого прототипирования торговых идей: открыл браузер, вставил код — и сразу увидел результат на графике. Особенно важно, что платформа не требует установки дополнительных библиотек, терминалов и настроек, как это часто бывает с Python или AmiBroker. Всё работает «из коробки», а язык Pine Script — простой и интуитивный, особенно если вы уже знакомы с техническим анализом.
К тому же, код можно легко адаптировать и поделиться ссылкой на него, чтобы каждый мог протестировать его самостоятельно — даже без подписки на платные функции. У меня есть реферальная ссылка TradingView: если вы зарегистрируетесь по ней, это немного поддержит меня, а вам будет всё то же самое.
Таймфреймы: дневной и часовой
Я протестировал стратегию на двух таймфреймах: дневном и часовом. Почему именно они?
Дневной — наиболее стабилен и подходит для анализа крупных движений. Его удобно использовать и тем, кто торгует вручную и не хочет реагировать на каждое движение внутри дня.
Часовой — даёт больше сигналов и позволяет точнее отследить динамику внутри тренда.
Мне кажется что для частного трейдера эти два масштаба — оптимальный компромисс между частотой сигналов и управляемостью стратегии.
Тестируемая логика
В стратегии используются два ключевых фильтра: наклон линии линейной регрессии и значение R². Условия простые: входим в позицию, если цена «прорывает» линию регрессии, а R² превышает заданный порог и растёт. Выход из позиции осуществляется по трейлинг-стопу, значение которого задаётся в процентах от экстремума.
Представленный код — моя интерпретация идеи из статьи Барбары Стар. Он может отличаться от оригинального примера из журнала, но отражает основную логику и добавляет реалистичные параметры торговли, включая комиссии и проскальзывание.
Результаты: день
Для дневного графика я включил настройку «Корректировать с учётом изменений контрактов» — такая корректировка убирает ценовые разрывы между контрактами (гэпы), возникающие при переходе от одного фьючерса к другому.
Торговля тремя контрактами фьючерсом на доллар/рубль (Si) на Мосбирже с комиссией 0.04% и проскальзыванием:
Подробности:
Результаты: час
На часовом таймфрейме стратегия охватила примерно полтора года котировок. Это позволяет увидеть, как система работает в разных фазах рынка: трендовых и боковых.
Торговля тремя контрактами фьючерсом на доллар/рубль (Si) на Мосбирже с комиссией 0.04% и проскальзыванием:
Подробности:
Верхняя панель — код стратегии:
//07.05.2025 // Стратегия на основе линейной регрессии и коэффициента детерминации R-squared // На основе https://traders.com/documentation/feedbk_docs/2007/12/Abstracts_new/Star/star.html // Михаил Шардин, https://shardin.name/?utm_source=tradingview //@version=6 strategy("Линейная регрессия + R² стратегия", overlay=true, commission_type=strategy.commission.percent, // Тип комиссии: процент commission_value=0.04, // Значение комиссии: 0.04% slippage=10, // Проскальзывание в тиках process_orders_on_close=true, default_qty_type=strategy.fixed, default_qty_value=3, initial_capital=500000) // Начальный капитал // === Входные параметры === // Параметры линейной регрессии src = input.source(hlcc4, title="Источник данных") len = input.int(defval=20, minval=1, title="Длина периода линейной регрессии") r_squared_threshold = input.float(0.2, title="Порог R-squared", step=0.01, minval=0) trailingStopOffset = input.float(3.9, "Отступ трейлинг-стопа (%)", step=0.1, minval=0.1) // Выбор таймфрейма для расчетов targetTimeframe = input.timeframe("", title="Таймфрейм для расчетов", tooltip="Укажите таймфрейм, на котором должны производиться расчеты") // === Расчеты линейной регрессии и R-squared === // Получаем данные с выбранного таймфрейма targetClose = request.security(syminfo.tickerid, targetTimeframe, src) // Расчет линии линейной регрессии lrc = ta.linreg(targetClose, len, 0) // Расчет коэффициента детерминации R-squared correlation_coeff = ta.correlation(targetClose, lrc, len) r_squared = math.pow(correlation_coeff, 2) // Обработка случая, когда стандартное отклонение источника равно нулю (все значения одинаковы) is_constant_src = ta.stdev(targetClose, len) == 0 r_squared_adjusted = is_constant_src ? 1.0 : r_squared // === Условия входа === // Условие для лонг: линия регрессии ниже цены закрытия и R² растет и выше порога long_condition = lrc < close and r_squared_adjusted > r_squared_adjusted[1] and r_squared_adjusted > r_squared_threshold // Условие для шорт: линия регрессии выше цены закрытия и R² растет и выше порога short_condition = lrc > close and r_squared_adjusted > r_squared_adjusted[1] and r_squared_adjusted > r_squared_threshold // === Трейлинг-стоп на основе процентного отступа === var float highestLongPrice = na var float lowestShortPrice = na var float trailingStopLong = na var float trailingStopShort = na // Флаг для блокировки новых сигналов при активной позиции var bool blockNewSignals = false // Обновляем трейлинг-стопы if (strategy.position_size > 0) // Если открыта длинная позиция highestLongPrice := na(highestLongPrice) ? close : math.max(highestLongPrice, close) trailingStopLong := highestLongPrice * (1 - trailingStopOffset / 100) blockNewSignals := true if (strategy.position_size < 0) // Если открыта короткая позиция lowestShortPrice := na(lowestShortPrice) ? close : math.min(lowestShortPrice, close) trailingStopShort := lowestShortPrice * (1 + trailingStopOffset / 100) blockNewSignals := true // Проверяем условия выхода if (strategy.position_size > 0 and close <= trailingStopLong) strategy.close("Лонг") highestLongPrice := na trailingStopLong := na blockNewSignals := false if (strategy.position_size < 0 and close >= trailingStopShort) strategy.close("Шорт") lowestShortPrice := na trailingStopShort := na blockNewSignals := false // === Открытие позиций (только если нет активной позиции) === if (long_condition and not blockNewSignals and strategy.position_size == 0) strategy.entry("Лонг", strategy.long) highestLongPrice := close trailingStopLong := close * (1 - trailingStopOffset / 100) blockNewSignals := true if (short_condition and not blockNewSignals and strategy.position_size == 0) strategy.entry("Шорт", strategy.short) lowestShortPrice := close trailingStopShort := close * (1 + trailingStopOffset / 100) blockNewSignals := true // === Отображение === // Отображение линии линейной регрессии plot(lrc, color = color.blue, title = "Линия линейной регрессии", style = plot.style_line, linewidth = 2) // Отображение трейлинг-стопов plot(strategy.position_size > 0 ? trailingStopLong : na, title="Лонг трейлинг-стоп", color=color.green, linewidth=2, style=plot.style_linebr) plot(strategy.position_size < 0 ? trailingStopShort : na, title="Шорт трейлинг-стоп", color=color.red, linewidth=2, style=plot.style_linebr) // Дополнительные информационные панели var table info = table.new(position.top_right, 3, 4, border_width=1) table.cell(info, 0, 0, "Linear Regression", bgcolor=color.new(color.blue, 90), text_color=color.white) table.cell(info, 0, 1, "Значение:", text_color=color.white) table.cell(info, 1, 1, str.tostring(lrc, "#.##"), text_color=color.white) table.cell(info, 0, 2, "R-squared:", text_color=color.white) table.cell(info, 1, 2, str.tostring(r_squared_adjusted, "#.####"), text_color=r_squared_adjusted > r_squared_threshold ? color.green : color.red) table.cell(info, 0, 3, "Сигнал:", text_color=color.white) table.cell(info, 1, 3, blockNewSignals ? "Заблокирован" : long_condition ? "ЛОНГ" : short_condition ? "ШОРТ" : "Нет сигнала", text_color=blockNewSignals ? color.yellow : long_condition ? color.green : short_condition ? color.red : color.white)
Нижняя панель — код индикатора:
// 07.05.2025 // Михаил Шардин, https://shardin.name/?utm_source=tradingview // На основе https://traders.com/documentation/feedbk_docs/2007/12/Abstracts_new/Star/star.html //@version=6 indicator(title="Коэффициент детерминации (R-squared)", shorttitle="R²", overlay=false, precision=4) // Настройки индикатора src = input.source(close, title="Источник данных") len = input.int(20, minval=2, title="Длина периода") // Для корреляции нужно минимум 2 точки // Выбор таймфрейма для расчетов targetTimeframe = input.timeframe("", title="Таймфрейм для расчетов", tooltip="Укажите таймфрейм, на котором должны производиться расчеты регрессии и R²") targetSrc = request.security(syminfo.tickerid, targetTimeframe, src, lookahead=barmerge.lookahead_on) // Расчет линии линейной регрессии (Ŷ - предсказанные значения) lrc = ta.linreg(targetSrc, len, 0) // Расчет коэффициента детерминации R-squared // R² = (Correlation(Y, Ŷ))² // Y - это targetSrc (фактические значения) // Ŷ - это lrc (предсказанные значения) correlation_coeff = ta.correlation(targetSrc, lrc, len) r_squared = math.pow(correlation_coeff, 2) // Обработка случая, когда стандартное отклонение источника равно нулю (все значения одинаковы) is_constant_src = ta.stdev(targetSrc, len) == 0 r_squared_adjusted = is_constant_src ? 1.0 : r_squared // Отображение R-squared на графике plot(r_squared_adjusted, color=color.new(color.blue, 0), title="R-squared", style=plot.style_line, linewidth=2) // Уровни для R-squared для лучшей визуальной интерпретации hline(1, "Идеальное соответствие", color.gray, linestyle=hline.style_dashed) hline(0.8, "Очень сильное", color.new(color.green, 50), linestyle=hline.style_dotted) hline(0.5, "Среднее", color.new(color.orange, 50), linestyle=hline.style_dotted) hline(0.2, "Слабое", color.new(color.red, 50), linestyle=hline.style_dotted) hline(0, "Нет соответствия", color.gray, linestyle=hline.style_dashed)
Тест показал: линейная регрессия с R² действительно может стать эффективным фильтром и подтверждением тренда — особенно на умеренных и длинных периодах.
Важно понимать, что простота кода не гарантирует прибыль — параметры требуют подстройки под инструмент, а условия рынка — постоянного внимания.
Тем не менее, эта стратегия может служить хорошей основой или фильтром внутри более сложной торговой системы.
Автор: Михаил Шардин
🔗 Моя онлайн-визитка
📢 Telegram «Умный Дом Инвестора»
13 мая 2025 г.
P.S. Критика только приветствуется — она помогает увидеть то, что упустил я.
Интересный подход к тренду, хотя по сути R2 к нему отношения не имеет, поскольку являеся собственно мерой соответствия регрессии к факту. Думаю, спорны предположения о начале движения в области роста от малых значений R2 — по этой же причине (грубо говоря, это сигнализирует прежде всего о росте нелинейности). При оптимизации потребуется учитывать дополнительные параметры (длина выборки и проч.). Но идея подхода интересная. Плюсую.
SergeyJu, в Pine Script функция ta.correlation(targetSrc, lrc, len) вычисляет коэффициент корреляции Пирсона, но на последних len барах, и значение пересчитывается на каждом новом баре. Это делает её «динамической».
И еще, интересно почему R2 не в лоб считают, а через корреляцию.
Найдите сами в вашем тексте ошибку. Очень важную!
Предложил бы на будущее на ваш выбор сделать анализ одного из фигурантов рейтинга. На пробу.
Что-то типа этого поста, но желательно со стандартными индикаторами и/или в Метатрейдере (это уже чисто личное).
Динамическими принято называть индикаторы, подстраивающие свои настройки (обычно период) под текущий рынок. Многие это правило нарушают (как и вы), от этого только путаница.
Что не забили в хомут, как большинство присутствующей публики.
И если была задача показать эффективность фильтра по лин.регрессии нужно было сравнить со стратегией без него, просто на одном том же самым выбранном вами значении трейлинг стопа.
если виртуально закрывать на каждом баре и открывать заново — статистики побольше наберётся, только проскальзывание и комис в перезаходах нужно занулить
а в целом — отлично, спасибо!
Стоит ММ поменять частоту, и вся конструкция летит в бездну.
Самая надежная ТС — это система, не зависящая от частоты колебаний цены.
А открытый интерес и вообще ужасно редко — хотя открытый интерес есть в данных трейдингвью.
Трейдер с опытом сразу видит тренды.
Особенно на длинных периодах.
Проблем с этим нет.
А вот вопрос определения слома тренда стоит куда острее.
Мы можем сказать, что это тренд, а не коррекция,
только если цена сильно уйдет от точки перегиба.
А пока она далеко не уйдет нет никаких способов определить,
что это будет тренд.
А вот коррекцию определить проще.
И случаются они чаще.
Очень интересно! Со стратегией пока не разобрался. Тупо скопировал ваш код в бесплатный аккаунт. Получилось 21 сделка на часовике с 14 августа 2023го. Результат -4% (
Михаил Шардин, Попробую скрин сюда поместить )
Михаил Шардин,
Хоббит, у вас инструмент другой. У меня склеенный фьючерс, а у вас нет. Должен быть: https://ru.tradingview.com/chart/?symbol=RUS%3ASI1%21
Михаил очень интересно читать ваши исследования. Буду с нетерпением ждать новости. У меня есть своя простая стратегия. Используются специальные SAR. Возможно они есть в TradingView. Надо будет посмотреть.
Только что проверил на CNYRUB_TOM с 28.03.2024. Получилось +20% Будет ориентиром )
В переменной highestLongPrice хранится уровень входа? От неё надо считать реальный уровень входа highestLongPrice * (1+commission_value). Потом так же для выхода. И складывать реальные вход с выходом.
Тогда возможно показанная доходность в 26% существенно снизится.
2. Очень неустойчивый результат из 4 всего прибыльных сделок. Предположим, не хватило бы один раз несколько пунктов и прибыльная сделка в итоге укатилась бы в убыточную. А это ещё минус 11% от преставленного результата. Что останется? Не минус ли?
svgr, комиссия в условиях задана была:
Ваш абзац отражён у меня фразой «Переменная комиссии установлена.»
Повторюсь, а где код её использования? Как я понимаю использование в своём посте я кратко описал.
svgr, у вас очень точные вопросы, перед тем как ответить на которые надо думать.
Вот мой ответ: в Pine Script использование commission_type, commission_value и slippage в strategy() влияет на симуляцию сделок и, соответственно, на итоговую метрику доходности в бэктесте. То есть, платформа TradingView при расчёте доходности учитывает комиссию и проскальзывание автоматически, без необходимости вручную корректировать цены входа/выхода.
На счёт 4х прибыльных сделок на интервале теста — да, это мало. Это скорее технический прототип, иллюстрирующий идею: как можно комбинировать линейную регрессию с коэффициентом детерминации R² и трейлинг-стопом.
Все они не позволяют зацепить глубинное явление тренда вовремя. Повезло с фазой рынка — в существенном плюсе, не повезло — в некотором минусе. Любой из методов.
А возьмёте вы не 1,5 года, а 8, например. Может вообще не быть прибыльных параметров у метода, постоянных на весь период. Даже после подгонки.
Я лет 10 назад искал метод, который будет успевать перестраиваться на новое направление тренда вслед за рынком, имея итоговый плюс за каждый год. То есть систематически уменьшить запаздывание.
Скорее всего методами усреднения прошлых данных и регрессиями это в принципе невозможно.
Хотя три года назад пришёл к некоей системе, которая давала запаздывание поменьше, чем в известных методах. Чтоб убедиться, что этого было бы достаточно для плюса почти каждый год хотя бы, осталось набрать ещё часть кода и протестировать. Но в последний год было не до этого.
Сейчас буду это всё заново набирать в OsEngine вместе с изучением платформы.
Вы, если хотите, напишите в ЛС, и реализуйте на другой платформе параллельно. За месяц поэтапно мог бы изложить подход.
svgr, в итоге я переезжаю на связку Raspberry Pi и Backtrader. Прямо сейчас статью дописываю, во вторник опубликую.
А можно вам в телеграм написать? Вы пользуетесь?