Открыть на GitHub

Стратегия BW WiseMan-1 (прорыв по Биллу Вильямсу)

Стратегия представляет собой порт MetaTrader-советника Exp_BW-wiseMan-1 на платформу StockSharp. Она автоматизирует логику паттерна WiseMan-1: сделка формируется, когда завершившаяся свеча выходит за пределы линий «Аллигатора» Билла Вильямса и одновременно обновляет локальные максимумы или минимумы. Дополнительный режим «против тренда» меняет местами сигналы, позволяя торговать на отбой от тех же прорывов.

Основная идея

  • Рассчитать линии «Аллигатора» как сглаженные скользящие средние от медианной цены (High + Low) / 2.
  • Сдвинуть челюсть, зубы и губы вперёд на заданное количество баров, чтобы сохранить визуализацию оригинального индикатора.
  • Подтверждать прорыв только тогда, когда текущая свеча превосходит экстремумы последних Back баров, что отфильтровывает случайные шумы.
  • Позволить задерживать исполнение на указанное число завершённых свечей параметром SignalBar.

Правила торговли

Сигналы на покупку

  1. Максимум свечи должен находиться ниже всех трёх линий Аллигатора (челюсти, зубов и губ).
  2. Закрытие обязано располагаться в верхней половине свечи, то есть выше её медианы.
  3. Минимум текущей свечи должен быть строго ниже минимумов последних Back баров.
  4. После того как сигнал становится активным:
    • При включённом параметре «Закрывать шорты» закрыть все короткие позиции.
    • При включённом параметре «Открывать лонги» и отсутствии позиций открыть новую длинную позицию.

Сигналы на продажу

  1. Минимум свечи должен находиться выше всех линий Аллигатора.
  2. Закрытие располагается в нижней половине свечи, то есть ниже медианы.
  3. Максимум текущей свечи превосходит максимумы последних Back баров.
  4. После активации сигнала:
    • При включённом параметре «Закрывать лонги» закрыть все длинные позиции.
    • При включённом параметре «Открывать шорты» и нулевой позиции открыть новую короткую позицию.

Режим против тренда

Если параметр «Counter-Trend Mode» включён, условия покупки и продажи меняются местами, и стратегия торгует против направления прорыва.

Параметры

  • Candle Type – таймфрейм, на котором строятся свечи и рассчитываются индикаторы (по умолчанию 1 час).
  • Counter-Trend Mode – переключение в контртрендовый режим (по умолчанию включено, как в оригинальном советнике).
  • Breakout Depth (Back) – количество предыдущих баров, по которым проверяется обновление экстремумов (по умолчанию 2).
  • Jaw Length / Shift – период сглаженной средней для челюсти и величина сдвига вперёд (13 / 8 по умолчанию).
  • Teeth Length / Shift – период и сдвиг для зубов (8 / 5).
  • Lips Length / Shift – период и сдвиг для губ (5 / 3).
  • Signal Bar – сколько завершённых свечей подождать перед исполнением сигнала (по умолчанию 1).
  • Enable Long / Enable Short – разрешения на открытие длинных и коротких позиций.
  • Close Long / Close Short – разрешения на закрытие противоположных позиций при появлении сигнала.

Дополнительные замечания

  • Стратегия использует рыночные заявки и не выставляет фиксированные стопы или тейк-профиты. Выход происходит только по встречным сигналам или при отключении соответствующих параметров.
  • Все вычисления ведутся по завершённым свечам — внутрисессионные данные игнорируются, что соответствует поведению оригинального эксперта MT5.
  • Размер позиции определяется общими настройками StockSharp. При необходимости измените базовый объём в конфигурации платформы.
using System;
using System.Collections.Generic;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Bill Williams Wiseman 1 strategy using Alligator-style triple EMA crossover.
/// </summary>
public class BwWiseman1Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _jawPeriod;
	private readonly StrategyParam<int> _teethPeriod;
	private readonly StrategyParam<int> _lipsPeriod;

	private decimal? _prevLips;
	private decimal? _prevTeeth;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int JawPeriod
	{
		get => _jawPeriod.Value;
		set => _jawPeriod.Value = value;
	}

	public int TeethPeriod
	{
		get => _teethPeriod.Value;
		set => _teethPeriod.Value = value;
	}

	public int LipsPeriod
	{
		get => _lipsPeriod.Value;
		set => _lipsPeriod.Value = value;
	}

	public BwWiseman1Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_jawPeriod = Param(nameof(JawPeriod), 34)
			.SetGreaterThanZero()
			.SetDisplay("Jaw Period", "Slow EMA (Jaw)", "Indicators");

		_teethPeriod = Param(nameof(TeethPeriod), 13)
			.SetGreaterThanZero()
			.SetDisplay("Teeth Period", "Medium EMA (Teeth)", "Indicators");

		_lipsPeriod = Param(nameof(LipsPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("Lips Period", "Fast EMA (Lips)", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevLips = null;
		_prevTeeth = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevLips = null;
		_prevTeeth = null;

		var jaw = new SmoothedMovingAverage { Length = JawPeriod };
		var teeth = new SmoothedMovingAverage { Length = TeethPeriod };
		var lips = new SmoothedMovingAverage { Length = LipsPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(jaw, teeth, lips, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, jaw);
			DrawIndicator(area, teeth);
			DrawIndicator(area, lips);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal jawVal, decimal teethVal, decimal lipsVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevLips = lipsVal;
			_prevTeeth = teethVal;
			return;
		}

		if (_prevLips == null || _prevTeeth == null)
		{
			_prevLips = lipsVal;
			_prevTeeth = teethVal;
			return;
		}

		// Lips crosses above teeth → buy
		if (_prevLips.Value <= _prevTeeth.Value && lipsVal > teethVal)
		{
			if (Position < 0)
				BuyMarket();
			if (Position <= 0)
				BuyMarket();
		}
		// Lips crosses below teeth → sell
		else if (_prevLips.Value >= _prevTeeth.Value && lipsVal < teethVal)
		{
			if (Position > 0)
				SellMarket();
			if (Position >= 0)
				SellMarket();
		}

		_prevLips = lipsVal;
		_prevTeeth = teethVal;
	}
}