BullsBears EA: алгоритмический подход к торговле на пересечении силы быков и медведей
Аннотация
В данной статье рассматривается архитектура и торговая логика советника BullsBears EA, реализованного для платформы MetaTrader 5. В основе алгоритма лежит классический принцип сопоставления силы покупателей и продавцов через индикаторы Bulls Power и Bears Power, дополненный многоуровневой системой фильтрации ложных сигналов, адаптивным управлением позицией и опциональным механизмом усреднения. Отдельное внимание уделено вопросу масштабирования объёма сделки по мере роста депозита — реализованному через ступенчатую, а не линейную функцию реинвестирования. Мы последовательно разбираем каждый структурный блок советника, приводим фрагменты исходного кода, иллюстрирующие принятые архитектурные решения, и описываем методологию тестирования в Strategy Tester. Статья ориентирована как на практикующих трейдеров, оценивающих применимость алгоритма, так и на разработчиков, интересующихся инженерными решениями при построении советников на MQL5.
Автор: Ilia Stavrov · Версия советника: 8.0 · Инструмент тестирования: XAUUSD.
Введение
Автоматизированная торговля на финансовых рынках регулярно сталкивается с одной и той же фундаментальной проблемой: индикатор, взятый в изоляции, почти всегда генерирует избыточное количество сигналов, значительная часть которых является рыночным шумом. Это особенно заметно на волатильных инструментах, к которым относится золото (XAUUSD) — именно на нём разрабатывался и тестировался BullsBears EA.
Индикаторы Bulls Power и Bears Power, предложенные Александром Элдером, измеряют способность быков и медведей толкать цену выше или ниже экспоненциальной скользящей средней. Идея использовать соотношение их модулей в качестве базового сигнала интуитивно понятна: если сила быков ощутимо превышает силу медведей — рынок склонен к росту, и наоборот. Однако практическое применение этой идеи «в лоб» сталкивается с классическими проблемами — торговлей во флэте, входами против доминирующего тренда и открытием позиций на пике импульсного движения, сразу после которого часто следует откат.
Цель данной работы — показать, как эти проблемы решаются на уровне архитектуры советника: через комбинацию нескольких независимых фильтров, каждый из которых отвечает за свой класс рыночных условий, а не пытается быть универсальным.
2. Теоретические основы
2.1. Bulls Power и Bears Power
Оба индикатора строятся на основе экспоненциальной скользящей средней (EMA) и рассчитываются как:
Bulls Power = High − EMA(Close, N) Bears Power = Low − EMA(Close, N)
где N — период индикатора (в советнике задаётся параметром InpBBPeriod, по умолчанию 13). Положительное значение Bulls Power говорит о том, что покупатели способны толкать цену выше средней, отрицательное значение Bears Power — что продавцы способны толкать её ниже средней. Именно соотношение модулей этих величин используется советником как первичный сигнал.
2.2. Вспомогательные индикаторы
Помимо силы быков/медведей, советник опирается ещё на три индикатора, каждый из которых закрывает свой класс рисков:
- EMA (InpEMAPeriod, по умолчанию 200) — определение доминирующего тренда;
- ATR (InpATRPeriod, по умолчанию 14) — измерение текущей волатильности для отсечения флэтовых участков рынка;
- RSI (InpRSIPeriod, по умолчанию 14) — защита от входа на перекупленности/перепроданности.
Такое сочетание нельзя назвать оригинальным по отдельности — каждый из индикаторов широко известен. Интересна именно их совместная работа в виде последовательной цепочки фильтров, о которой пойдёт речь ниже.
3. Архитектура сигнальной подсистемы
Ядро сигнала предельно компактно — это сознательное архитектурное решение: базовая логика должна быть тривиально проверяемой, а вся сложность выносится в отдельные, независимо тестируемые функции-фильтры.
int CalcSignal(double b, double br) { double ab = MathAbs(b), abr = MathAbs(br); if(ab > abr) return 1; // сила быков больше — сигнал BUY if(abr > ab) return -1; // сила медведей больше — сигнал SELL return 0; }
Обратите внимание, что функция ничего не знает ни о фильтрах, ни о состоянии позиций — она решает ровно одну задачу. Это соответствует принципу единственной ответственности и заметно упрощает как отладку, так и последующую модификацию логики.
Сигнал не приводит к немедленному открытию позиции. Он должен продержаться заданное число баров подряд (InpConfirmBars), прежде чем советник ему «поверит» — эта логика реализована через простую машину состояний с флагами ожидания (g_waiting, g_pendingSig, g_confirmCnt), которая сбрасывается при любом нарушении условий подтверждения.
4. Система фильтров входа
4.1. Фильтр волатильности (защита от флэта)
Идея фильтра проста: если текущая волатильность заметно ниже своего недавнего среднего значения, рынок находится в состоянии консолидации, и торговый сигнал, полученный в такой момент, статистически менее надёжен.
bool CheckATR(double &a[]) { if(InpATRMinMult <= 0) return true; // фильтр отключён double avg = 0; int n = ArraySize(a); for(int i = 1; i < n; i++) avg += a[i]; avg /= (n - 1); return a[0] >= InpATRMinMult * avg; }
Показательная деталь реализации: текущее значение ATR (a[0]) исключается из расчёта собственного среднего (цикл начинается с i = 1) — это позволяет избежать самокорреляции и делает сравнение корректным «текущее значение против независимо посчитанного среднего», а не «значение против среднего, в которое оно само же входит».
4.2. Фильтр перекупленности/перепроданности
bool CheckRSI(int sig, double rsiVal) { if(InpRSIPeriod <= 0) return true; if(sig == 1 && rsiVal >= InpRSI_OB) return false; // покупка на перекупленности запрещена if(sig == -1 && rsiVal <= InpRSI_OS) return false; // продажа на перепроданности запрещена return true; }
Здесь важно, что фильтр асимметричен по направлению: RSI выше уровня перекупленности блокирует именно покупки, а не любые сделки — продажа на перекупленном рынке фильтром не запрещена, что логически согласуется с самой идеей индикатора.
4.3. Фильтр дистанции от EMA
Этот фильтр решает менее очевидную, но практически значимую проблему: резкое импульсное движение может дать формально корректный сигнал по Bulls/Bears Power, при этом цена уже находится в перерастянутом состоянии относительно своей средней, и статистически более вероятен откат, чем продолжение движения.
bool CheckEMADist(double ema, double atr) { if(InpEMADistMult <= 0 || atr <= 0) return true; double c = iClose(_Symbol, InpTimeframe, 1); double dist = MathAbs(c - ema); return dist <= InpEMADistMult * atr; }
Существенное архитектурное решение здесь — измерение дистанции не в фиксированных пунктах, а в единицах ATR. Это делает фильтр адаптивным к текущей волатильности: то, что является «нормальной» дистанцией от средней на спокойном рынке, может быть уже «перерастянутым» состоянием на волатильном участке, и наоборот. Фиксированный порог в пунктах для такой задачи был бы заведомо хуже.
4.4. Совокупное решение
Все фильтры объединяются конъюнкцией — сигнал принимается к рассмотрению только тогда, когда одобрены абсолютно все активные проверки:
bool trendOK = CheckTrend(sig, ema[0]); bool atrOK = CheckATR(atr); bool rsiOK = CheckRSI(sig, rsi[0]); bool distOK = CheckEMADist(ema[0], atr[0]); bool filtersOK = trendOK && atrOK && rsiOK && distOK;
Каждый фильтр можно независимо отключить, обнулив соответствующий параметр — это позволяет проводить сравнительное тестирование вклада каждого конкретного фильтра в итоговый результат, не трогая остальную логику советника.
5. Управление позицией
После открытия позиции советник переходит к её сопровождению. Логика построена в порядке приоритета: сначала проверяется закрытие по достижении общего тейк-профита сетки, затем — перевод в безубыток и трейлинг, и только в последнюю очередь — защитный стоп-лосс от худшего ордера сетки.
Наиболее интересна с инженерной точки зрения функция перевода в безубыток с последующим трейлингом, поскольку обе задачи решаются единым алгоритмом, без дублирования кода:
void ApplyBUTrail(PosInfo &p, bool buy, double profPt, double pt) { double beLevel = buy ? NormalizeDouble(p.avgPx + InpBE_Offset*pt, _Digits) : NormalizeDouble(p.avgPx - InpBE_Offset*pt, _Digits); double targetSL = beLevel; if(InpTrailStep > 0) { double profAfterBE = profPt - InpBE_Trigger; if(profAfterBE >= InpTrailStep) { int steps = (int)MathFloor(profAfterBE / InpTrailStep); targetSL = buy ? NormalizeDouble(beLevel + steps*InpTrailStep*pt, _Digits) : NormalizeDouble(beLevel - steps*InpTrailStep*pt, _Digits); } } // ... применение к позициям с проверкой SLDistOK и "improved" }
Ключевое решение — расчёт трейлинга не как непрерывного слежения за ценой, а как дискретных шагов (steps = floor(profAfterBE / InpTrailStep)) от уже зафиксированного уровня безубытка. Это даёт два практических эффекта: во-первых, стоп не «дёргается» на каждом тике, а переставляется только при прохождении целого шага; во-вторых, требование "улучшения" (новый стоп обязан быть строго лучше текущего) исключает случайное ухудшение защиты позиции при откате цены внутри уже пройденного шага.
6. Сеточная подсистема усреднения
Если параметр InpGridMax превышает 1, советник переходит в режим усреднения. Логика добавления нового ордера в сетку объединяет сразу три независимые проверки — ценовой шаг от худшего ордера, интервал проверки спреда и лимит количества ордеров:
void TryGridAdd(int dir) { int cnt = CountPosByDir(dir); if(cnt >= InpGridMax || cnt == 0) return; // ... поиск худшего ордера в данном направлении (wp) double distPt = buy ? (wp - ask)/pt : (bid - wp)/pt; if(distPt < InpGridStep) return; // ценовой шаг ещё не пройден datetime now = TimeCurrent(); if(InpGridSpreadSec > 0) { if(now - g_lastGridCheck < InpGridSpreadSec) return; // ещё не время проверять спред g_lastGridCheck = now; if(!CheckSpread()) return; // спред слишком широкий } else if(!CheckSpread()) return; double lot = CalcLot(); if(!CheckMargin(dir, lot)) return; // ... открытие ордера }
Отдельного комментария заслуживает Троттлинг проверки спреда через InpGridSpreadSec. Наивная реализация проверяла бы спред на каждом тике, что на волатильном инструменте вроде золота может приводить к систематическому пропуску добавления ордера в моменты кратковременного расширения спреда, даже если в среднем по интервалу спред приемлем. Троттлинг с фиксированным интервалом сглаживает эту проблему, не отказываясь от проверки полностью.
7. Ступенчатый реинвест
Одно из более нетривиальных архитектурных решений советника — способ масштабирования лота вслед за ростом депозита. Вместо линейной пропорции (лот = баланс × коэффициент), что является наиболее очевидным и распространённым подходом, реализовано ступенчатое удвоение:
double CalcLot() { if(InpReinvest) { double bal = AccountInfoDouble(ACCOUNT_BALANCE); double base = InpStartBalance > 0 ? InpStartBalance : 100.0; int doublings = 0; double thresh = base * 2.0; while(bal >= thresh && doublings < 20) { doublings++; thresh *= 2.0; } double lot = InpStartLot; for(int i = 0; i < doublings; i++) lot *= 2.0; return NormLot(lot); } return NormLot(InpStartLot); }
Разница между линейным и ступенчатым подходом принципиальна. Линейное масштабирование пересчитывает лот на каждой сделке, из-за чего объём становится очень чувствителен к локальным колебаниям баланса — просадка в 5% немедленно и пропорционально уменьшает объём следующей сделки, что может создавать эффект «дожимания» стратегии в невыгодный момент. Ступенчатая модель, напротив, фиксирует объём до тех пор, пока баланс не преодолеет очередной порог удвоения — это делает размер позиции более инертным к краткосрочному шуму эквити и приближает поведение объёма к тому, как управляют капиталом дискреционные трейдеры: не пересматривая размер позиции после каждой сделки, а периодически пересматривая его при существенном изменении капитала.
Отдельно стоит отметить последующую нормализацию лота через NormLot() — расчётное значение всегда приводится к допустимым брокером границам объёма (минимум, максимум, шаг), что делает функцию безопасной независимо от того, насколько «круглым» получилось теоретическое значение после нескольких удвоений.
8. Управление рисками на уровне счёта
Помимо сделочных TP/SL, советник содержит два независимых предохранителя уровня счёта — по максимальной просадке и по-минимальному эквити, — оба из которых влияют только на разрешение новых первых входов и не закрывают уже открытые позиции принудительно:
bool CheckAccountLimits() { double balance = AccountInfoDouble(ACCOUNT_BALANCE); double equity = AccountInfoDouble(ACCOUNT_EQUITY); if(InpMinEquity > 0 && equity < InpMinEquity) return false; if(InpMaxDD > 0 && balance > 0) { double ddPct = (balance - equity) / balance * 100.0; if(ddPct > InpMaxDD) return false; } return true; }
Такое разделение ответственности («новые входы» vs «управление открытыми позициями») сознательное: полная принудительная остановка управления уже открытыми сделками при достижении лимита просадки могла бы, наоборот, зафиксировать убыток в невыгодный момент — тогда как логика TP/SL/безубытка продолжает работать независимо от состояния этого предохранителя.
9. Методология тестирования
Тестирование проводилось в MetaTrader 5 Strategy Tester на инструменте XAUUSD с использованием режима хеджирования счёта. Для получения репрезентативной картины рекомендуется тестировать советник по модели «каждый тик на основе реальных тиков» — для инструмента с высокой волатильностью вроде золота разница в качестве симуляции между этой моделью и упрощёнными моделями (OHLC, открытие цены) может быть существенной, особенно для сеточной логики, где точность внутрибарового движения цены напрямую влияет на момент срабатывания усреднения.
Примеры результатов тестирования за период 2026.01.01 - 2026.07.03, таймфрейм М10, модель тестирования «каждый тик на основе реальных тиков», кредитное плечо 500, сет файл в приложении. Результаты тестирования представлены на скриншотах.
9.1. Тест в одиночном режиме
9.2. Тест в сеточном режиме
9.3. Тест ступенчатого реинвеста
10. Обсуждение ограничений
Честное изложение архитектуры было бы неполным без обозначения границ применимости алгоритма.
Во-первых, сеточное усреднение по своей природе предполагает временное нахождение позиции в минусе до момента усреднения и последующего выхода по средней цене — это не является дефектом реализации, а является неотъемлемым свойством самого класса стратегий, и требует соответствующего запаса свободной маржи.
Во-вторых, ступенчатый реинвест — это механизм масштабирования капитала, а не механизм повышения прибыльности сигнала как такового: он одинаково ускоряет как рост, так и просадку счёта. Корректная работа реинвеста требует, чтобы параметр стартового баланса соответствовал фактическому балансу счёта на момент запуска советника — расхождение этих величин приводит к некорректному расчёту количества «удвоений» и, как следствие, к неожиданно большому или маленькому объёму сделки с самого начала работы.
В-третьих, все представленные в статье пороговые значения фильтров являются отправной точкой, полученной в ходе тестирования на конкретном инструменте и историческом периоде, а не универсальной константой — при изменении инструмента, таймфрейма или рыночного режима параметры требуют повторной калибровки.
11. Заключение
BullsBears EA представляет собой пример построения торгового алгоритма по принципу композиции независимых, тестируемых по отдельности компонентов: базовый сигнал, отделённый от логики фильтрации; фильтры, отделённые от логики управления позицией; управление позицией, отделённое от логики масштабирования объёма. Такая архитектура делает советника удобным полигоном для дальнейших экспериментов — каждый блок можно исследовать, отключать или заменять, не затрагивая остальные части системы.
Дальнейшие направления развития включают адаптивную калибровку порогов фильтров на основе скользящей статистики рынка, а также альтернативные модели управления объёмом позиции, не привязанные исключительно к балансу счёта.
Список источников
- Elder A. Trading for a Living. — John Wiley & Sons, 1993. (описание индикаторов Bulls Power / Bears Power)
- Wilder J. W. New Concepts in Technical Trading Systems. — Trend Research, 1978. (индикаторы ATR, RSI)
- Документация MQL5 Reference: https://www.mql5.com/ru/docs
Материал подготовлен автором советника BullsBears EA. Полное руководство пользователя и готовые наборы параметров доступны в разделе «Обсуждение» на странице продукта в MQL5 Market.







